diff --git a/site/app/Application/Error.php b/site/app/Application/Error.php index 8a8030fde..cf5816fd6 100644 --- a/site/app/Application/Error.php +++ b/site/app/Application/Error.php @@ -3,7 +3,6 @@ namespace MichalSpacekCz\Application; -use MichalSpacekCz\ShouldNotHappenException; use Nette\Application\BadRequestException; use Nette\Application\Helpers; use Nette\Application\Request; @@ -28,12 +27,6 @@ public function response(Request $request): Response if ($e instanceof BadRequestException) { [$module, , $sep] = Helpers::splitName($request->getPresenterName()); - if (!is_string($module)) { - throw new ShouldNotHappenException(sprintf('Module should be a string, %s provided', get_debug_type($module))); - } - if (!is_string($sep)) { - throw new ShouldNotHappenException(sprintf('Separator should be a string, %s provided', get_debug_type($sep))); - } return new ForwardResponse($request->setPresenterName($module . $sep . 'Error')); } diff --git a/site/app/Application/LinkGenerator.php b/site/app/Application/LinkGenerator.php new file mode 100644 index 000000000..e4ac49766 --- /dev/null +++ b/site/app/Application/LinkGenerator.php @@ -0,0 +1,34 @@ + $args + * @throws InvalidLinkException + */ + public function link(string $destination, array $args = [], ?NetteLinkGenerator $linkGenerator = null): string + { + $link = ($linkGenerator ?? $this->linkGenerator)->link($destination, $args); + if ($link === null) { + throw new ShouldNotHappenException('Link should be a string, null returned'); + } + return $link; + } + +} diff --git a/site/app/Application/Locale/LocaleLinkGenerator.php b/site/app/Application/Locale/LocaleLinkGenerator.php index 762ec1e6d..438432540 100644 --- a/site/app/Application/Locale/LocaleLinkGenerator.php +++ b/site/app/Application/Locale/LocaleLinkGenerator.php @@ -4,10 +4,11 @@ namespace MichalSpacekCz\Application\Locale; use Contributte\Translation\Translator; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\Application\Routing\RouterFactory; use MichalSpacekCz\ShouldNotHappenException; use Nette\Application\IPresenterFactory; -use Nette\Application\LinkGenerator; +use Nette\Application\LinkGenerator as NetteLinkGenerator; use Nette\Application\Routers\RouteList; use Nette\Application\UI\InvalidLinkException; use Nette\Http\IRequest; @@ -52,12 +53,12 @@ public function links(string $destination, array $params = []): array throw new ShouldNotHappenException(sprintf("The presenter should be a '%s' but it's a %s", RouteList::class, get_debug_type($router))); } if (count($router->getRouters())) { - $linkGenerator = new LinkGenerator($router, $this->httpRequest->getUrl(), $this->presenterFactory); + $linkGenerator = new NetteLinkGenerator($router, $this->httpRequest->getUrl(), $this->presenterFactory); $links[$locale] = new LocaleLink( $locale, $this->languages[$locale]['code'], $this->languages[$locale]['name'], - $linkGenerator->link($destination, $this->getParams($params, $locale)), + $this->linkGenerator->link($destination, $this->getParams($params, $locale), $linkGenerator), ); } } diff --git a/site/app/Articles/Blog/BlogPostFactory.php b/site/app/Articles/Blog/BlogPostFactory.php index 0d4bf2e0e..284118e40 100644 --- a/site/app/Articles/Blog/BlogPostFactory.php +++ b/site/app/Articles/Blog/BlogPostFactory.php @@ -5,6 +5,7 @@ use Contributte\Translation\Translator; use DateTime; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\Application\Locale\LocaleLinkGenerator; use MichalSpacekCz\DateTime\Exceptions\InvalidTimezoneException; use MichalSpacekCz\Formatter\TexyFormatter; @@ -14,7 +15,6 @@ use MichalSpacekCz\Utils\Exceptions\JsonItemNotStringException; use MichalSpacekCz\Utils\Exceptions\JsonItemsNotArrayException; use MichalSpacekCz\Utils\JsonUtils; -use Nette\Application\LinkGenerator; use Nette\Application\UI\InvalidLinkException; use Nette\Database\Row; use Nette\Utils\JsonException; diff --git a/site/app/Form/RegenerateTokensFormFactory.php b/site/app/Form/RegenerateTokensFormFactory.php index 8c7aad47c..88a3739d6 100644 --- a/site/app/Form/RegenerateTokensFormFactory.php +++ b/site/app/Form/RegenerateTokensFormFactory.php @@ -3,8 +3,8 @@ namespace MichalSpacekCz\Form; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\User\Manager; -use Nette\Application\LinkGenerator; use Nette\Http\Session; use Nette\Security\User; use Nette\Utils\Html; diff --git a/site/app/Form/TalkFormFactory.php b/site/app/Form/TalkFormFactory.php index 6468a0561..9a96af6d5 100644 --- a/site/app/Form/TalkFormFactory.php +++ b/site/app/Form/TalkFormFactory.php @@ -3,12 +3,12 @@ namespace MichalSpacekCz\Form; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\Application\Locale\Locales; use MichalSpacekCz\Form\Controls\TrainingControlsFactory; use MichalSpacekCz\Media\VideoThumbnails; use MichalSpacekCz\Talks\Talk; use MichalSpacekCz\Talks\Talks; -use Nette\Application\LinkGenerator; use Nette\Forms\Controls\SubmitButton; use Nette\Utils\Html; use Nette\Utils\Strings; diff --git a/site/app/Tls/CertificatesApiClient.php b/site/app/Tls/CertificatesApiClient.php index 71baa2781..eb18b75f5 100644 --- a/site/app/Tls/CertificatesApiClient.php +++ b/site/app/Tls/CertificatesApiClient.php @@ -3,13 +3,13 @@ namespace MichalSpacekCz\Tls; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\Application\ServerEnv; use MichalSpacekCz\DateTime\Exceptions\CannotParseDateTimeException; use MichalSpacekCz\Http\Client\HttpClient; use MichalSpacekCz\Http\Client\HttpClientRequest; use MichalSpacekCz\Http\Exceptions\HttpClientRequestException; use MichalSpacekCz\Tls\Exceptions\CertificatesApiException; -use Nette\Application\LinkGenerator; use Nette\Application\UI\InvalidLinkException; use Nette\Schema\Expect; use Nette\Schema\Processor; diff --git a/site/app/User/Manager.php b/site/app/User/Manager.php index 3fc55dd28..492ddede1 100644 --- a/site/app/User/Manager.php +++ b/site/app/User/Manager.php @@ -5,6 +5,7 @@ use DateTimeInterface; use Exception; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\Database\TypedDatabase; use MichalSpacekCz\Http\Cookies\CookieName; use MichalSpacekCz\Http\Cookies\Cookies; @@ -13,7 +14,6 @@ use MichalSpacekCz\User\Exceptions\IdentityNotSimpleIdentityException; use MichalSpacekCz\User\Exceptions\IdentityUsernameNotStringException; use MichalSpacekCz\User\Exceptions\IdentityWithoutUsernameException; -use Nette\Application\LinkGenerator; use Nette\Database\Explorer; use Nette\Database\UniqueConstraintViolationException; use Nette\Http\IRequest; diff --git a/site/composer.lock b/site/composer.lock index 85735ac75..77cbb41bc 100644 --- a/site/composer.lock +++ b/site/composer.lock @@ -180,16 +180,16 @@ }, { "name": "nette/application", - "version": "v3.2.1", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/nette/application.git", - "reference": "bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2" + "reference": "0af99e2fe9fcab0ed598da9f03992b936375d071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/application/zipball/bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2", - "reference": "bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2", + "url": "https://api.github.com/repos/nette/application/zipball/0af99e2fe9fcab0ed598da9f03992b936375d071", + "reference": "0af99e2fe9fcab0ed598da9f03992b936375d071", "shasum": "" }, "require": { @@ -266,22 +266,22 @@ ], "support": { "issues": "https://github.com/nette/application/issues", - "source": "https://github.com/nette/application/tree/v3.2.1" + "source": "https://github.com/nette/application/tree/v3.2.3" }, - "time": "2024-03-11T19:57:03+00:00" + "time": "2024-04-20T00:45:39+00:00" }, { "name": "nette/bootstrap", - "version": "v3.2.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "226e2df81b25207e382de3e89315eb17f4fe24a7" + "reference": "5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/226e2df81b25207e382de3e89315eb17f4fe24a7", - "reference": "226e2df81b25207e382de3e89315eb17f4fe24a7", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c", + "reference": "5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c", "shasum": "" }, "require": { @@ -347,9 +347,9 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/v3.2.2" + "source": "https://github.com/nette/bootstrap/tree/v3.2.3" }, - "time": "2024-03-10T22:15:04+00:00" + "time": "2024-04-19T00:07:13+00:00" }, { "name": "nette/caching", @@ -1477,23 +1477,23 @@ }, { "name": "paragonie/halite", - "version": "v5.1.0", + "version": "v5.1.1", "source": { "type": "git", "url": "https://github.com/paragonie/halite.git", - "reference": "666f9770a12bd8a49cefba16c759baa4843dbf5a" + "reference": "a8f6c884db11fc6e4d3a533aa3ed596361a16221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/halite/zipball/666f9770a12bd8a49cefba16c759baa4843dbf5a", - "reference": "666f9770a12bd8a49cefba16c759baa4843dbf5a", + "url": "https://api.github.com/repos/paragonie/halite/zipball/a8f6c884db11fc6e4d3a533aa3ed596361a16221", + "reference": "a8f6c884db11fc6e4d3a533aa3ed596361a16221", "shasum": "" }, "require": { "ext-json": "*", "paragonie/constant_time_encoding": "^2", "paragonie/hidden-string": "^1|^2", - "paragonie/sodium_compat": "^1.17", + "paragonie/sodium_compat": "^1|^2", "php": "^8.1" }, "require-dev": { @@ -1542,9 +1542,9 @@ "support": { "docs": "https://github.com/paragonie/halite/tree/master/doc", "issues": "https://github.com/paragonie/halite/issues", - "source": "https://github.com/paragonie/halite/tree/v5.1.0" + "source": "https://github.com/paragonie/halite/tree/v5.1.1" }, - "time": "2022-05-23T05:02:50+00:00" + "time": "2024-04-19T23:29:37+00:00" }, { "name": "paragonie/hidden-string", @@ -3474,16 +3474,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.66", + "version": "1.10.67", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", - "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", "shasum": "" }, "require": { @@ -3526,13 +3526,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-03-28T16:17:31+00:00" + "time": "2024-04-16T07:22:02+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -3685,12 +3681,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "31f373849a62ccfe23cba594e91b488e3ec2270b" + "reference": "a6fb2a760c95f45ccb6a412599770061c330c624" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/31f373849a62ccfe23cba594e91b488e3ec2270b", - "reference": "31f373849a62ccfe23cba594e91b488e3ec2270b", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a6fb2a760c95f45ccb6a412599770061c330c624", + "reference": "a6fb2a760c95f45ccb6a412599770061c330c624", "shasum": "" }, "conflict": { @@ -3844,7 +3840,7 @@ "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", "ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.06,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<4.1.1", @@ -3875,7 +3871,8 @@ "friendsofsymfony/oauth2-php": "<1.3", "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", - "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", + "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", + "friendsofsymfony1/symfony1": ">=1.1,<1.15.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.3", @@ -3996,7 +3993,7 @@ "mantisbt/mantisbt": "<2.26.1", "marcwillmann/turn": "<0.3.3", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.3", + "mautic/core": "<4.4.12|>=5.0.0.0-alpha,<5.0.4", "mediawiki/core": "<1.36.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", @@ -4140,7 +4137,7 @@ "really-simple-plugins/complianz-gdpr": "<6.4.2", "redaxo/source": "<=5.15.1", "remdex/livehelperchat": "<4.29", - "reportico-web/reportico": "<=7.1.21", + "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.7", "rmccue/requests": ">=1.6,<1.8", "robrichards/xmlseclibs": ">=1,<3.0.4", @@ -4211,7 +4208,7 @@ "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "swag/paypal": "<5.4.4", - "swiftmailer/swiftmailer": ">=4,<5.4.5", + "swiftmailer/swiftmailer": ">=4,<6.2.5", "swiftyedit/swiftyedit": "<1.2", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", @@ -4261,7 +4258,7 @@ "t3s/content-consent": "<1.0.3|>=2,<2.0.2", "tastyigniter/tastyigniter": "<3.3", "tcg/voyager": "<=1.4", - "tecnickcom/tcpdf": "<6.2.22", + "tecnickcom/tcpdf": "<=6.7.4", "terminal42/contao-tablelookupwizard": "<3.3.5", "thelia/backoffice-default-template": ">=2.1,<2.1.2", "thelia/thelia": ">=2.1,<2.1.3", @@ -4269,6 +4266,7 @@ "thinkcmf/thinkcmf": "<=5.1.7", "thorsten/phpmyfaq": "<3.2.2", "tikiwiki/tiki-manager": "<=17.1", + "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", @@ -4322,6 +4320,7 @@ "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", "winter/wn-backend-module": "<1.2.4", + "winter/wn-dusk-plugin": "<2.1", "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", "woocommerce/woocommerce": "<6.6", @@ -4423,20 +4422,20 @@ "type": "tidelift" } ], - "time": "2024-04-09T19:04:27+00:00" + "time": "2024-04-19T20:04:33+00:00" }, { "name": "shipmonk/composer-dependency-analyser", - "version": "1.4.0", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/shipmonk-rnd/composer-dependency-analyser.git", - "reference": "da787b1ec7e02e618f080e65f944a72a47a4696c" + "reference": "701b48519fd097732635d0083d5731724e413120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shipmonk-rnd/composer-dependency-analyser/zipball/da787b1ec7e02e618f080e65f944a72a47a4696c", - "reference": "da787b1ec7e02e618f080e65f944a72a47a4696c", + "url": "https://api.github.com/repos/shipmonk-rnd/composer-dependency-analyser/zipball/701b48519fd097732635d0083d5731724e413120", + "reference": "701b48519fd097732635d0083d5731724e413120", "shasum": "" }, "require": { @@ -4447,6 +4446,9 @@ "require-dev": { "editorconfig-checker/editorconfig-checker": "^10.3.0", "ergebnis/composer-normalize": "^2.19", + "ext-dom": "*", + "ext-libxml": "*", + "phpcompatibility/php-compatibility": "^9.3", "phpstan/phpstan": "^1.10.63", "phpstan/phpstan-phpunit": "^1.1.1", "phpstan/phpstan-strict-rules": "^1.2.3", @@ -4484,9 +4486,9 @@ ], "support": { "issues": "https://github.com/shipmonk-rnd/composer-dependency-analyser/issues", - "source": "https://github.com/shipmonk-rnd/composer-dependency-analyser/tree/1.4.0" + "source": "https://github.com/shipmonk-rnd/composer-dependency-analyser/tree/1.5.2" }, - "time": "2024-03-19T15:39:35+00:00" + "time": "2024-04-17T08:30:48+00:00" }, { "name": "slevomat/coding-standard", @@ -4648,16 +4650,16 @@ }, { "name": "spaze/phpstan-disallowed-calls", - "version": "v3.1.2", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/spaze/phpstan-disallowed-calls.git", - "reference": "d0b3d66d53fe581889c6f5927cacc5aed8249c34" + "reference": "6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spaze/phpstan-disallowed-calls/zipball/d0b3d66d53fe581889c6f5927cacc5aed8249c34", - "reference": "d0b3d66d53fe581889c6f5927cacc5aed8249c34", + "url": "https://api.github.com/repos/spaze/phpstan-disallowed-calls/zipball/6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535", + "reference": "6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535", "shasum": "" }, "require": { @@ -4666,10 +4668,10 @@ }, "require-dev": { "nette/neon": "^3.2", - "nikic/php-parser": "^4.13", + "nikic/php-parser": "^4.13 || ^5.0", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^8.5 || ^10.1", + "phpunit/phpunit": "^8.5 || ^10.1 || ^11.0", "spaze/coding-standard": "^1.7" }, "type": "phpstan-extension", @@ -4702,7 +4704,7 @@ ], "support": { "issues": "https://github.com/spaze/phpstan-disallowed-calls/issues", - "source": "https://github.com/spaze/phpstan-disallowed-calls/tree/v3.1.2" + "source": "https://github.com/spaze/phpstan-disallowed-calls/tree/v3.2.0" }, "funding": [ { @@ -4710,7 +4712,7 @@ "type": "github" } ], - "time": "2024-02-13T18:26:25+00:00" + "time": "2024-04-21T02:16:39+00:00" }, { "name": "spaze/phpstan-disallowed-calls-nette", diff --git a/site/config/services.neon b/site/config/services.neon index dac1ed059..8ef9b41e4 100644 --- a/site/config/services.neon +++ b/site/config/services.neon @@ -4,6 +4,7 @@ services: type: MichalSpacekCz\Application\Cli\CliArgs imported: true - MichalSpacekCz\Application\Error + - MichalSpacekCz\Application\LinkGenerator localeLinkGenerator: MichalSpacekCz\Application\Locale\LocaleLinkGenerator(languages: %locales.languages%) - MichalSpacekCz\Application\Locale\Locales - MichalSpacekCz\Application\MappingCheck\ApplicationMappingCheck diff --git a/site/phpstan-latte-templates.neon b/site/phpstan-latte-templates.neon index 3bdbe5dab..39756c4cc 100644 --- a/site/phpstan-latte-templates.neon +++ b/site/phpstan-latte-templates.neon @@ -14,6 +14,7 @@ parameters: - # `{input "applications-{$application->id}" ...}` is an `$application->id` input in `applications` container https://github.com/efabrica-team/phpstan-latte/pull/380 or https://github.com/efabrica-team/phpstan-latte/issues/400 message: '#^Call to an undefined method Nette\\Forms\\Controls\\BaseControl\|Statuses_[a-f0-9]+_applications::getControl\(\)#' path: app/Admin/Presenters/templates/Trainings/date.latte + - "#^Presenter name must be alphanumeric string, '' is invalid\\.$#" includes: - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/site/phpstan-vendor.neon b/site/phpstan-vendor.neon index 056bd04c8..84542ef0e 100644 --- a/site/phpstan-vendor.neon +++ b/site/phpstan-vendor.neon @@ -13,6 +13,7 @@ parameters: - vendor/shipmonk/composer-dependency-analyser/src/Analyser.php # throws No error to ignore is reported on line - vendor/shipmonk/composer-dependency-analyser/src/Cli.php # throws No error to ignore is reported on line - vendor/shipmonk/composer-dependency-analyser/src/ComposerJson.php # throws No error to ignore is reported on line + - vendor/shipmonk/composer-dependency-analyser/src/UsedSymbolExtractor.php # throws No error to ignore is reported on line - vendor/spaze/sri-macros/src/Bridges/Latte/Nodes/SriNode.php # throws No error to ignore is reported on line - vendor/spaze/svg-icons-latte/src/Nodes/IconNode.php # throws No error to ignore is reported on line - vendor/symfony/translation-contracts/Test/* # Symfony packages not installed @@ -82,6 +83,12 @@ parameters: message: 'use some logger instead' allowIn: - vendor/efabrica/phpstan-latte/tests/*.php # https://github.com/efabrica-team/phpstan-latte/pull/232 + - + function: 'phpinfo()' + message: 'might reveal session id or other tokens in cookies' + allowIn: + - vendor/spaze/phpinfo/src/PhpInfo.php + - vendor/tracy/tracy/src/Tracy/BlueScreen/BlueScreen.php # bundled disallowed-execution-calls.neon - function: 'exec()' diff --git a/site/tests/Application/LinkGeneratorTest.phpt b/site/tests/Application/LinkGeneratorTest.phpt new file mode 100644 index 000000000..d7df436cf --- /dev/null +++ b/site/tests/Application/LinkGeneratorTest.phpt @@ -0,0 +1,36 @@ +linkGenerator->link('Www:Homepage:'); + }); + Assert::exception(function (): void { + $this->linkGenerator->link(''); + }, InvalidLinkException::class, "Invalid destination ''."); + } + +} + +TestCaseRunner::run(LinkGeneratorTest::class); diff --git a/site/tests/Application/Locale/LocaleLinkGeneratorTest.phpt b/site/tests/Application/Locale/LocaleLinkGeneratorTest.phpt index a4d7e79db..b907a4eb8 100644 --- a/site/tests/Application/Locale/LocaleLinkGeneratorTest.phpt +++ b/site/tests/Application/Locale/LocaleLinkGeneratorTest.phpt @@ -4,11 +4,11 @@ declare(strict_types = 1); namespace MichalSpacekCz\Application\Locale; +use MichalSpacekCz\Application\LinkGenerator; use MichalSpacekCz\Application\Routing\RouterFactory; use MichalSpacekCz\Test\NoOpTranslator; use MichalSpacekCz\Test\TestCaseRunner; use Nette\Application\IPresenterFactory; -use Nette\Application\LinkGenerator; use Nette\Application\UI\InvalidLinkException; use Nette\Http\IRequest; use Tester\Assert; diff --git a/site/vendor/composer/autoload_classmap.php b/site/vendor/composer/autoload_classmap.php index 171566ef7..d10caa5d4 100644 --- a/site/vendor/composer/autoload_classmap.php +++ b/site/vendor/composer/autoload_classmap.php @@ -229,8 +229,10 @@ 'Nette\\Application\\Application' => $vendorDir . '/nette/application/src/Application/Application.php', 'Nette\\Application\\ApplicationException' => $vendorDir . '/nette/application/src/Application/exceptions.php', 'Nette\\Application\\Attributes\\CrossOrigin' => $vendorDir . '/nette/application/src/Application/Attributes/CrossOrigin.php', + 'Nette\\Application\\Attributes\\Deprecated' => $vendorDir . '/nette/application/src/Application/Attributes/Deprecated.php', 'Nette\\Application\\Attributes\\Parameter' => $vendorDir . '/nette/application/src/Application/Attributes/Parameter.php', 'Nette\\Application\\Attributes\\Persistent' => $vendorDir . '/nette/application/src/Application/Attributes/Persistent.php', + 'Nette\\Application\\Attributes\\Requires' => $vendorDir . '/nette/application/src/Application/Attributes/Requires.php', 'Nette\\Application\\BadRequestException' => $vendorDir . '/nette/application/src/Application/exceptions.php', 'Nette\\Application\\ForbiddenRequestException' => $vendorDir . '/nette/application/src/Application/exceptions.php', 'Nette\\Application\\Helpers' => $vendorDir . '/nette/application/src/Application/Helpers.php', @@ -254,6 +256,8 @@ 'Nette\\Application\\Routers\\Route' => $vendorDir . '/nette/application/src/Application/Routers/Route.php', 'Nette\\Application\\Routers\\RouteList' => $vendorDir . '/nette/application/src/Application/Routers/RouteList.php', 'Nette\\Application\\Routers\\SimpleRouter' => $vendorDir . '/nette/application/src/Application/Routers/SimpleRouter.php', + 'Nette\\Application\\SwitchException' => $vendorDir . '/nette/application/src/Application/exceptions.php', + 'Nette\\Application\\UI\\AccessPolicy' => $vendorDir . '/nette/application/src/Application/UI/AccessPolicy.php', 'Nette\\Application\\UI\\BadSignalException' => $vendorDir . '/nette/application/src/Application/UI/BadSignalException.php', 'Nette\\Application\\UI\\Component' => $vendorDir . '/nette/application/src/Application/UI/Component.php', 'Nette\\Application\\UI\\ComponentReflection' => $vendorDir . '/nette/application/src/Application/UI/ComponentReflection.php', @@ -268,6 +272,7 @@ 'Nette\\Application\\UI\\Link' => $vendorDir . '/nette/application/src/Application/UI/Link.php', 'Nette\\Application\\UI\\MethodReflection' => $vendorDir . '/nette/application/src/Application/UI/MethodReflection.php', 'Nette\\Application\\UI\\Multiplier' => $vendorDir . '/nette/application/src/Application/UI/Multiplier.php', + 'Nette\\Application\\UI\\ParameterConverter' => $vendorDir . '/nette/application/src/Application/UI/ParameterConverter.php', 'Nette\\Application\\UI\\Presenter' => $vendorDir . '/nette/application/src/Application/UI/Presenter.php', 'Nette\\Application\\UI\\PresenterComponent' => $vendorDir . '/nette/application/src/compatibility.php', 'Nette\\Application\\UI\\PresenterComponentReflection' => $vendorDir . '/nette/application/src/compatibility.php', diff --git a/site/vendor/composer/autoload_static.php b/site/vendor/composer/autoload_static.php index 06588b9e8..4716797d1 100644 --- a/site/vendor/composer/autoload_static.php +++ b/site/vendor/composer/autoload_static.php @@ -432,8 +432,10 @@ class ComposerStaticInit247de957f14f643f393d210a332dd05b 'Nette\\Application\\Application' => __DIR__ . '/..' . '/nette/application/src/Application/Application.php', 'Nette\\Application\\ApplicationException' => __DIR__ . '/..' . '/nette/application/src/Application/exceptions.php', 'Nette\\Application\\Attributes\\CrossOrigin' => __DIR__ . '/..' . '/nette/application/src/Application/Attributes/CrossOrigin.php', + 'Nette\\Application\\Attributes\\Deprecated' => __DIR__ . '/..' . '/nette/application/src/Application/Attributes/Deprecated.php', 'Nette\\Application\\Attributes\\Parameter' => __DIR__ . '/..' . '/nette/application/src/Application/Attributes/Parameter.php', 'Nette\\Application\\Attributes\\Persistent' => __DIR__ . '/..' . '/nette/application/src/Application/Attributes/Persistent.php', + 'Nette\\Application\\Attributes\\Requires' => __DIR__ . '/..' . '/nette/application/src/Application/Attributes/Requires.php', 'Nette\\Application\\BadRequestException' => __DIR__ . '/..' . '/nette/application/src/Application/exceptions.php', 'Nette\\Application\\ForbiddenRequestException' => __DIR__ . '/..' . '/nette/application/src/Application/exceptions.php', 'Nette\\Application\\Helpers' => __DIR__ . '/..' . '/nette/application/src/Application/Helpers.php', @@ -457,6 +459,8 @@ class ComposerStaticInit247de957f14f643f393d210a332dd05b 'Nette\\Application\\Routers\\Route' => __DIR__ . '/..' . '/nette/application/src/Application/Routers/Route.php', 'Nette\\Application\\Routers\\RouteList' => __DIR__ . '/..' . '/nette/application/src/Application/Routers/RouteList.php', 'Nette\\Application\\Routers\\SimpleRouter' => __DIR__ . '/..' . '/nette/application/src/Application/Routers/SimpleRouter.php', + 'Nette\\Application\\SwitchException' => __DIR__ . '/..' . '/nette/application/src/Application/exceptions.php', + 'Nette\\Application\\UI\\AccessPolicy' => __DIR__ . '/..' . '/nette/application/src/Application/UI/AccessPolicy.php', 'Nette\\Application\\UI\\BadSignalException' => __DIR__ . '/..' . '/nette/application/src/Application/UI/BadSignalException.php', 'Nette\\Application\\UI\\Component' => __DIR__ . '/..' . '/nette/application/src/Application/UI/Component.php', 'Nette\\Application\\UI\\ComponentReflection' => __DIR__ . '/..' . '/nette/application/src/Application/UI/ComponentReflection.php', @@ -471,6 +475,7 @@ class ComposerStaticInit247de957f14f643f393d210a332dd05b 'Nette\\Application\\UI\\Link' => __DIR__ . '/..' . '/nette/application/src/Application/UI/Link.php', 'Nette\\Application\\UI\\MethodReflection' => __DIR__ . '/..' . '/nette/application/src/Application/UI/MethodReflection.php', 'Nette\\Application\\UI\\Multiplier' => __DIR__ . '/..' . '/nette/application/src/Application/UI/Multiplier.php', + 'Nette\\Application\\UI\\ParameterConverter' => __DIR__ . '/..' . '/nette/application/src/Application/UI/ParameterConverter.php', 'Nette\\Application\\UI\\Presenter' => __DIR__ . '/..' . '/nette/application/src/Application/UI/Presenter.php', 'Nette\\Application\\UI\\PresenterComponent' => __DIR__ . '/..' . '/nette/application/src/compatibility.php', 'Nette\\Application\\UI\\PresenterComponentReflection' => __DIR__ . '/..' . '/nette/application/src/compatibility.php', diff --git a/site/vendor/composer/installed.json b/site/vendor/composer/installed.json index e3b52ed1b..ca8e53746 100644 --- a/site/vendor/composer/installed.json +++ b/site/vendor/composer/installed.json @@ -345,17 +345,17 @@ }, { "name": "nette/application", - "version": "v3.2.1", - "version_normalized": "3.2.1.0", + "version": "v3.2.3", + "version_normalized": "3.2.3.0", "source": { "type": "git", "url": "https://github.com/nette/application.git", - "reference": "bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2" + "reference": "0af99e2fe9fcab0ed598da9f03992b936375d071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/application/zipball/bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2", - "reference": "bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2", + "url": "https://api.github.com/repos/nette/application/zipball/0af99e2fe9fcab0ed598da9f03992b936375d071", + "reference": "0af99e2fe9fcab0ed598da9f03992b936375d071", "shasum": "" }, "require": { @@ -389,7 +389,7 @@ "latte/latte": "Allows using Latte in templates", "nette/forms": "Allows to use Nette\\Application\\UI\\Form" }, - "time": "2024-03-11T19:57:03+00:00", + "time": "2024-04-20T00:45:39+00:00", "type": "library", "extra": { "branch-alias": { @@ -434,23 +434,23 @@ ], "support": { "issues": "https://github.com/nette/application/issues", - "source": "https://github.com/nette/application/tree/v3.2.1" + "source": "https://github.com/nette/application/tree/v3.2.3" }, "install-path": "../nette/application" }, { "name": "nette/bootstrap", - "version": "v3.2.2", - "version_normalized": "3.2.2.0", + "version": "v3.2.3", + "version_normalized": "3.2.3.0", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "226e2df81b25207e382de3e89315eb17f4fe24a7" + "reference": "5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/226e2df81b25207e382de3e89315eb17f4fe24a7", - "reference": "226e2df81b25207e382de3e89315eb17f4fe24a7", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c", + "reference": "5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c", "shasum": "" }, "require": { @@ -480,7 +480,7 @@ "nette/robot-loader": "to use Configurator::createRobotLoader()", "tracy/tracy": "to use Configurator::enableTracy()" }, - "time": "2024-03-10T22:15:04+00:00", + "time": "2024-04-19T00:07:13+00:00", "type": "library", "extra": { "branch-alias": { @@ -518,7 +518,7 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/v3.2.2" + "source": "https://github.com/nette/bootstrap/tree/v3.2.3" }, "install-path": "../nette/bootstrap" }, @@ -1774,31 +1774,31 @@ }, { "name": "paragonie/halite", - "version": "v5.1.0", - "version_normalized": "5.1.0.0", + "version": "v5.1.1", + "version_normalized": "5.1.1.0", "source": { "type": "git", "url": "https://github.com/paragonie/halite.git", - "reference": "666f9770a12bd8a49cefba16c759baa4843dbf5a" + "reference": "a8f6c884db11fc6e4d3a533aa3ed596361a16221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/halite/zipball/666f9770a12bd8a49cefba16c759baa4843dbf5a", - "reference": "666f9770a12bd8a49cefba16c759baa4843dbf5a", + "url": "https://api.github.com/repos/paragonie/halite/zipball/a8f6c884db11fc6e4d3a533aa3ed596361a16221", + "reference": "a8f6c884db11fc6e4d3a533aa3ed596361a16221", "shasum": "" }, "require": { "ext-json": "*", "paragonie/constant_time_encoding": "^2", "paragonie/hidden-string": "^1|^2", - "paragonie/sodium_compat": "^1.17", + "paragonie/sodium_compat": "^1|^2", "php": "^8.1" }, "require-dev": { "phpunit/phpunit": "^9", "vimeo/psalm": "^4" }, - "time": "2022-05-23T05:02:50+00:00", + "time": "2024-04-19T23:29:37+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1842,7 +1842,7 @@ "support": { "docs": "https://github.com/paragonie/halite/tree/master/doc", "issues": "https://github.com/paragonie/halite/issues", - "source": "https://github.com/paragonie/halite/tree/v5.1.0" + "source": "https://github.com/paragonie/halite/tree/v5.1.1" }, "install-path": "../paragonie/halite" }, @@ -2121,17 +2121,17 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.66", - "version_normalized": "1.10.66.0", + "version": "1.10.67", + "version_normalized": "1.10.67.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "94779c987e4ebd620025d9e5fdd23323903950bd" + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd", - "reference": "94779c987e4ebd620025d9e5fdd23323903950bd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", "shasum": "" }, "require": { @@ -2140,7 +2140,7 @@ "conflict": { "phpstan/phpstan-shim": "*" }, - "time": "2024-03-28T16:17:31+00:00", + "time": "2024-04-16T07:22:02+00:00", "bin": [ "phpstan", "phpstan.phar" @@ -2176,10 +2176,6 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], "install-path": "../phpstan/phpstan" @@ -2506,12 +2502,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "31f373849a62ccfe23cba594e91b488e3ec2270b" + "reference": "a6fb2a760c95f45ccb6a412599770061c330c624" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/31f373849a62ccfe23cba594e91b488e3ec2270b", - "reference": "31f373849a62ccfe23cba594e91b488e3ec2270b", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a6fb2a760c95f45ccb6a412599770061c330c624", + "reference": "a6fb2a760c95f45ccb6a412599770061c330c624", "shasum": "" }, "conflict": { @@ -2665,7 +2661,7 @@ "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", "ezsystems/ezplatform-user": ">=1,<1.0.1", "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.06,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<4.1.1", @@ -2696,7 +2692,8 @@ "friendsofsymfony/oauth2-php": "<1.3", "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", - "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", + "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", + "friendsofsymfony1/symfony1": ">=1.1,<1.15.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.3", @@ -2817,7 +2814,7 @@ "mantisbt/mantisbt": "<2.26.1", "marcwillmann/turn": "<0.3.3", "matyhtf/framework": "<3.0.6", - "mautic/core": "<4.3", + "mautic/core": "<4.4.12|>=5.0.0.0-alpha,<5.0.4", "mediawiki/core": "<1.36.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", @@ -2961,7 +2958,7 @@ "really-simple-plugins/complianz-gdpr": "<6.4.2", "redaxo/source": "<=5.15.1", "remdex/livehelperchat": "<4.29", - "reportico-web/reportico": "<=7.1.21", + "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.7", "rmccue/requests": ">=1.6,<1.8", "robrichards/xmlseclibs": ">=1,<3.0.4", @@ -3032,7 +3029,7 @@ "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "swag/paypal": "<5.4.4", - "swiftmailer/swiftmailer": ">=4,<5.4.5", + "swiftmailer/swiftmailer": ">=4,<6.2.5", "swiftyedit/swiftyedit": "<1.2", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", @@ -3082,7 +3079,7 @@ "t3s/content-consent": "<1.0.3|>=2,<2.0.2", "tastyigniter/tastyigniter": "<3.3", "tcg/voyager": "<=1.4", - "tecnickcom/tcpdf": "<6.2.22", + "tecnickcom/tcpdf": "<=6.7.4", "terminal42/contao-tablelookupwizard": "<3.3.5", "thelia/backoffice-default-template": ">=2.1,<2.1.2", "thelia/thelia": ">=2.1,<2.1.3", @@ -3090,6 +3087,7 @@ "thinkcmf/thinkcmf": "<=5.1.7", "thorsten/phpmyfaq": "<3.2.2", "tikiwiki/tiki-manager": "<=17.1", + "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", "tinymce/tinymce": "<7", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", @@ -3143,6 +3141,7 @@ "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", "winter/wn-backend-module": "<1.2.4", + "winter/wn-dusk-plugin": "<2.1", "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", "woocommerce/woocommerce": "<6.6", @@ -3208,7 +3207,7 @@ "zfr/zfr-oauth2-server-module": "<0.1.2", "zoujingli/thinkadmin": "<=6.1.53" }, - "time": "2024-04-09T19:04:27+00:00", + "time": "2024-04-19T20:04:33+00:00", "default-branch": true, "type": "metapackage", "notification-url": "https://packagist.org/downloads/", @@ -3249,17 +3248,17 @@ }, { "name": "shipmonk/composer-dependency-analyser", - "version": "1.4.0", - "version_normalized": "1.4.0.0", + "version": "1.5.2", + "version_normalized": "1.5.2.0", "source": { "type": "git", "url": "https://github.com/shipmonk-rnd/composer-dependency-analyser.git", - "reference": "da787b1ec7e02e618f080e65f944a72a47a4696c" + "reference": "701b48519fd097732635d0083d5731724e413120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shipmonk-rnd/composer-dependency-analyser/zipball/da787b1ec7e02e618f080e65f944a72a47a4696c", - "reference": "da787b1ec7e02e618f080e65f944a72a47a4696c", + "url": "https://api.github.com/repos/shipmonk-rnd/composer-dependency-analyser/zipball/701b48519fd097732635d0083d5731724e413120", + "reference": "701b48519fd097732635d0083d5731724e413120", "shasum": "" }, "require": { @@ -3270,6 +3269,9 @@ "require-dev": { "editorconfig-checker/editorconfig-checker": "^10.3.0", "ergebnis/composer-normalize": "^2.19", + "ext-dom": "*", + "ext-libxml": "*", + "phpcompatibility/php-compatibility": "^9.3", "phpstan/phpstan": "^1.10.63", "phpstan/phpstan-phpunit": "^1.1.1", "phpstan/phpstan-strict-rules": "^1.2.3", @@ -3277,7 +3279,7 @@ "shipmonk/name-collision-detector": "^2.0.0", "slevomat/coding-standard": "^8.0.1" }, - "time": "2024-03-19T15:39:35+00:00", + "time": "2024-04-17T08:30:48+00:00", "bin": [ "bin/composer-dependency-analyser" ], @@ -3309,7 +3311,7 @@ ], "support": { "issues": "https://github.com/shipmonk-rnd/composer-dependency-analyser/issues", - "source": "https://github.com/shipmonk-rnd/composer-dependency-analyser/tree/1.4.0" + "source": "https://github.com/shipmonk-rnd/composer-dependency-analyser/tree/1.5.2" }, "install-path": "../shipmonk/composer-dependency-analyser" }, @@ -3821,17 +3823,17 @@ }, { "name": "spaze/phpstan-disallowed-calls", - "version": "v3.1.2", - "version_normalized": "3.1.2.0", + "version": "v3.2.0", + "version_normalized": "3.2.0.0", "source": { "type": "git", "url": "https://github.com/spaze/phpstan-disallowed-calls.git", - "reference": "d0b3d66d53fe581889c6f5927cacc5aed8249c34" + "reference": "6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spaze/phpstan-disallowed-calls/zipball/d0b3d66d53fe581889c6f5927cacc5aed8249c34", - "reference": "d0b3d66d53fe581889c6f5927cacc5aed8249c34", + "url": "https://api.github.com/repos/spaze/phpstan-disallowed-calls/zipball/6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535", + "reference": "6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535", "shasum": "" }, "require": { @@ -3840,13 +3842,13 @@ }, "require-dev": { "nette/neon": "^3.2", - "nikic/php-parser": "^4.13", + "nikic/php-parser": "^4.13 || ^5.0", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^8.5 || ^10.1", + "phpunit/phpunit": "^8.5 || ^10.1 || ^11.0", "spaze/coding-standard": "^1.7" }, - "time": "2024-02-13T18:26:25+00:00", + "time": "2024-04-21T02:16:39+00:00", "type": "phpstan-extension", "extra": { "phpstan": { @@ -3878,7 +3880,7 @@ ], "support": { "issues": "https://github.com/spaze/phpstan-disallowed-calls/issues", - "source": "https://github.com/spaze/phpstan-disallowed-calls/tree/v3.1.2" + "source": "https://github.com/spaze/phpstan-disallowed-calls/tree/v3.2.0" }, "funding": [ { diff --git a/site/vendor/composer/installed.php b/site/vendor/composer/installed.php index 7da38374f..083c96c2b 100644 --- a/site/vendor/composer/installed.php +++ b/site/vendor/composer/installed.php @@ -92,18 +92,18 @@ 'dev_requirement' => true, ), 'nette/application' => array( - 'pretty_version' => 'v3.2.1', - 'version' => '3.2.1.0', - 'reference' => 'bb59a51bc36b7561a6e44d1f014e0e3d68e23dc2', + 'pretty_version' => 'v3.2.3', + 'version' => '3.2.3.0', + 'reference' => '0af99e2fe9fcab0ed598da9f03992b936375d071', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/application', 'aliases' => array(), 'dev_requirement' => false, ), 'nette/bootstrap' => array( - 'pretty_version' => 'v3.2.2', - 'version' => '3.2.2.0', - 'reference' => '226e2df81b25207e382de3e89315eb17f4fe24a7', + 'pretty_version' => 'v3.2.3', + 'version' => '3.2.3.0', + 'reference' => '5f8b9420b0b5441b55f0745dd0e8afa1653e5e6c', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/bootstrap', 'aliases' => array(), @@ -269,9 +269,9 @@ 'dev_requirement' => false, ), 'paragonie/halite' => array( - 'pretty_version' => 'v5.1.0', - 'version' => '5.1.0.0', - 'reference' => '666f9770a12bd8a49cefba16c759baa4843dbf5a', + 'pretty_version' => 'v5.1.1', + 'version' => '5.1.1.0', + 'reference' => 'a8f6c884db11fc6e4d3a533aa3ed596361a16221', 'type' => 'library', 'install_path' => __DIR__ . '/../paragonie/halite', 'aliases' => array(), @@ -341,9 +341,9 @@ 'dev_requirement' => true, ), 'phpstan/phpstan' => array( - 'pretty_version' => '1.10.66', - 'version' => '1.10.66.0', - 'reference' => '94779c987e4ebd620025d9e5fdd23323903950bd', + 'pretty_version' => '1.10.67', + 'version' => '1.10.67.0', + 'reference' => '16ddbe776f10da6a95ebd25de7c1dbed397dc493', 'type' => 'library', 'install_path' => __DIR__ . '/../phpstan/phpstan', 'aliases' => array(), @@ -418,7 +418,7 @@ 'roave/security-advisories' => array( 'pretty_version' => 'dev-latest', 'version' => 'dev-latest', - 'reference' => '31f373849a62ccfe23cba594e91b488e3ec2270b', + 'reference' => 'a6fb2a760c95f45ccb6a412599770061c330c624', 'type' => 'metapackage', 'install_path' => null, 'aliases' => array( @@ -427,9 +427,9 @@ 'dev_requirement' => true, ), 'shipmonk/composer-dependency-analyser' => array( - 'pretty_version' => '1.4.0', - 'version' => '1.4.0.0', - 'reference' => 'da787b1ec7e02e618f080e65f944a72a47a4696c', + 'pretty_version' => '1.5.2', + 'version' => '1.5.2.0', + 'reference' => '701b48519fd097732635d0083d5731724e413120', 'type' => 'library', 'install_path' => __DIR__ . '/../shipmonk/composer-dependency-analyser', 'aliases' => array(), @@ -526,9 +526,9 @@ 'dev_requirement' => false, ), 'spaze/phpstan-disallowed-calls' => array( - 'pretty_version' => 'v3.1.2', - 'version' => '3.1.2.0', - 'reference' => 'd0b3d66d53fe581889c6f5927cacc5aed8249c34', + 'pretty_version' => 'v3.2.0', + 'version' => '3.2.0.0', + 'reference' => '6d5ce7e951c6e9b22b9b6c14301ab9cf55e7b535', 'type' => 'phpstan-extension', 'install_path' => __DIR__ . '/../spaze/phpstan-disallowed-calls', 'aliases' => array(), diff --git a/site/vendor/nette/application/src/Application/Attributes/CrossOrigin.php b/site/vendor/nette/application/src/Application/Attributes/CrossOrigin.php index 6ddf9e4dd..e2b03c5ac 100644 --- a/site/vendor/nette/application/src/Application/Attributes/CrossOrigin.php +++ b/site/vendor/nette/application/src/Application/Attributes/CrossOrigin.php @@ -12,7 +12,14 @@ use Attribute; +/** + * Use Requires(sameOrigin: false) + */ #[Attribute(Attribute::TARGET_METHOD)] -class CrossOrigin +final class CrossOrigin extends Requires { + public function __construct() + { + parent::__construct(sameOrigin: false); + } } diff --git a/site/vendor/nette/application/src/Application/Attributes/Deprecated.php b/site/vendor/nette/application/src/Application/Attributes/Deprecated.php new file mode 100644 index 000000000..4de95693b --- /dev/null +++ b/site/vendor/nette/application/src/Application/Attributes/Deprecated.php @@ -0,0 +1,13 @@ +methods = $methods === null ? null : (array) $methods; + $this->actions = $actions === null ? null : (array) $actions; + } +} diff --git a/site/vendor/nette/application/src/Application/Helpers.php b/site/vendor/nette/application/src/Application/Helpers.php index 56abd45b8..5883d69c3 100644 --- a/site/vendor/nette/application/src/Application/Helpers.php +++ b/site/vendor/nette/application/src/Application/Helpers.php @@ -21,6 +21,7 @@ final class Helpers /** * Splits name into [module, presenter] or [presenter, action] + * @return array{string, string, string} */ public static function splitName(string $name): array { @@ -29,4 +30,24 @@ public static function splitName(string $name): array ? ['', $name, ''] : [substr($name, 0, $pos), substr($name, $pos + 1), ':']; } + + + /** + * return string[] + */ + public static function getClassesAndTraits(string $class): array + { + $res = [$class => $class] + class_parents($class); + $addTraits = function (string $type) use (&$res, &$addTraits): void { + $res += class_uses($type); + foreach (class_uses($type) as $trait) { + $addTraits($trait); + } + }; + foreach ($res as $type) { + $addTraits($type); + } + + return $res; + } } diff --git a/site/vendor/nette/application/src/Application/LinkGenerator.php b/site/vendor/nette/application/src/Application/LinkGenerator.php index c31b19161..ad2a70e7a 100644 --- a/site/vendor/nette/application/src/Application/LinkGenerator.php +++ b/site/vendor/nette/application/src/Application/LinkGenerator.php @@ -11,6 +11,7 @@ use Nette\Http\UrlScript; use Nette\Routing\Router; +use Nette\Utils\Reflection; /** @@ -18,6 +19,10 @@ */ final class LinkGenerator { + /** @internal */ + public ?Request $lastRequest = null; + + public function __construct( private readonly Router $router, private readonly UrlScript $refUrl, @@ -28,56 +33,249 @@ public function __construct( /** * Generates URL to presenter. - * @param string $dest in format "[[[module:]presenter:]action] [#fragment]" + * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this | @alias] [#fragment]" + * @throws UI\InvalidLinkException + */ + public function link( + string $destination, + array $args = [], + ?UI\Component $component = null, + ?string $mode = null, + ): ?string + { + $parts = self::parseDestination($destination); + $args = $parts['args'] ?? $args; + $request = $this->createRequest($component, $parts['path'] . ($parts['signal'] ? '!' : ''), $args, $mode ?? 'link'); + $relative = $mode === 'link' && !$parts['absolute'] && !$component?->getPresenter()->absoluteUrls; + return $mode === 'forward' || $mode === 'test' + ? null + : $this->requestToUrl($request, $relative) . $parts['fragment']; + } + + + /** + * @param string $destination in format "[[[module:]presenter:]action | signal! | this | @alias]" + * @param string $mode forward|redirect|link * @throws UI\InvalidLinkException + * @internal */ - public function link(string $dest, array $params = []): string + public function createRequest( + ?UI\Component $component, + string $destination, + array $args, + string $mode, + ): Request { - if (!preg_match('~^([\w:]+):(\w*+)(#.*)?()$~D', $dest, $m)) { - throw new UI\InvalidLinkException("Invalid link destination '$dest'."); + // note: createRequest supposes that saveState(), run() & tryCall() behaviour is final + + $this->lastRequest = null; + $refPresenter = $component?->getPresenter(); + $path = $destination; + + if (($component && !$component instanceof UI\Presenter) || str_ends_with($destination, '!')) { + [$cname, $signal] = Helpers::splitName(rtrim($destination, '!')); + if ($cname !== '') { + $component = $component->getComponent(strtr($cname, ':', '-')); + } + + if ($signal === '') { + throw new UI\InvalidLinkException('Signal must be non-empty string.'); + } + + $path = 'this'; } - [, $presenter, $action, $frag] = $m; + if ($path[0] === '@') { + if (!$this->presenterFactory instanceof PresenterFactory) { + throw new \LogicException('Link aliasing requires PresenterFactory service.'); + } + $path = ':' . $this->presenterFactory->getAlias(substr($path, 1)); + } - try { - $class = $this->presenterFactory?->getPresenterClass($presenter); - } catch (InvalidPresenterException $e) { - throw new UI\InvalidLinkException($e->getMessage(), 0, $e); + $current = false; + [$presenter, $action] = Helpers::splitName($path); + if ($presenter === '') { + if (!$refPresenter) { + throw new \LogicException("Presenter must be specified in '$destination'."); + } + $action = $path === 'this' ? $refPresenter->getAction() : $action; + $presenter = $refPresenter->getName(); + $presenterClass = $refPresenter::class; + + } else { + if ($presenter[0] === ':') { // absolute + $presenter = substr($presenter, 1); + if (!$presenter) { + throw new UI\InvalidLinkException("Missing presenter name in '$destination'."); + } + } elseif ($refPresenter) { // relative + [$module, , $sep] = Helpers::splitName($refPresenter->getName()); + $presenter = $module . $sep . $presenter; + } + + try { + $presenterClass = $this->presenterFactory?->getPresenterClass($presenter); + } catch (InvalidPresenterException $e) { + throw new UI\InvalidLinkException($e->getMessage(), 0, $e); + } } - if (is_subclass_of($class, UI\Presenter::class)) { + // PROCESS SIGNAL ARGUMENTS + if (isset($signal)) { // $component must be StatePersistent + $reflection = new UI\ComponentReflection($component::class); + if ($signal === 'this') { // means "no signal" + $signal = ''; + if (array_key_exists(0, $args)) { + throw new UI\InvalidLinkException("Unable to pass parameters to 'this!' signal."); + } + } elseif (!str_contains($signal, UI\Component::NameSeparator)) { + // counterpart of signalReceived() & tryCall() + + $method = $reflection->getSignalMethod($signal); + if (!$method) { + throw new UI\InvalidLinkException("Unknown signal '$signal', missing handler {$reflection->getName()}::{$component::formatSignalMethod($signal)}()"); + } elseif ($this->isDeprecated($refPresenter, $method)) { + trigger_error("Link to deprecated signal '$signal'" . ($component === $refPresenter ? '' : ' in ' . $component::class) . " from '{$refPresenter->getName()}:{$refPresenter->getAction()}'.", E_USER_DEPRECATED); + } + + // convert indexed parameters to named + UI\ParameterConverter::toParameters($method, $args, [], $missing); + } + + // counterpart of StatePersistent + if ($args && array_intersect_key($args, $reflection->getPersistentParams())) { + $component->saveState($args); + } + + if ($args && $component !== $refPresenter) { + $prefix = $component->getUniqueId() . UI\Component::NameSeparator; + foreach ($args as $key => $val) { + unset($args[$key]); + $args[$prefix . $key] = $val; + } + } + } + + // PROCESS ARGUMENTS + if (is_subclass_of($presenterClass, UI\Presenter::class)) { if ($action === '') { $action = UI\Presenter::DefaultAction; } - if ( - method_exists($class, $method = $class::formatActionMethod($action)) - || method_exists($class, $method = $class::formatRenderMethod($action)) - ) { - UI\Presenter::argsToParams($class, $method, $params, [], $missing); - if ($missing) { - $rp = $missing[0]; - throw new UI\InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()"); + $current = $refPresenter && ($action === '*' || strcasecmp($action, $refPresenter->getAction()) === 0) && $presenterClass === $refPresenter::class; + + $reflection = new UI\ComponentReflection($presenterClass); + if ($this->isDeprecated($refPresenter, $reflection)) { + trigger_error("Link to deprecated presenter '$presenter' from '{$refPresenter->getName()}:{$refPresenter->getAction()}'.", E_USER_DEPRECATED); + } + + // counterpart of run() & tryCall() + if ($method = $reflection->getActionRenderMethod($action)) { + if ($this->isDeprecated($refPresenter, $method)) { + trigger_error("Link to deprecated action '$presenter:$action' from '{$refPresenter->getName()}:{$refPresenter->getAction()}'.", E_USER_DEPRECATED); } - } elseif (array_key_exists(0, $params)) { - throw new UI\InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method."); + + UI\ParameterConverter::toParameters($method, $args, $path === 'this' ? $refPresenter->getParameters() : [], $missing); + + } elseif (array_key_exists(0, $args)) { + throw new UI\InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method $presenterClass::{$presenterClass::formatRenderMethod($action)}()."); + } + + // counterpart of StatePersistent + if ($refPresenter) { + if (empty($signal) && $args && array_intersect_key($args, $reflection->getPersistentParams())) { + $refPresenter->saveStatePartial($args, $reflection); + } + + $globalState = $refPresenter->getGlobalState($path === 'this' ? null : $presenterClass); + if ($current && $args) { + $tmp = $globalState + $refPresenter->getParameters(); + foreach ($args as $key => $val) { + if (http_build_query([$val]) !== (isset($tmp[$key]) ? http_build_query([$tmp[$key]]) : '')) { + $current = false; + break; + } + } + } + + $args += $globalState; } } - if ($action !== '') { - $params[UI\Presenter::ActionKey] = $action; + if ($mode !== 'test' && !empty($missing)) { + foreach ($missing as $rp) { + if (!array_key_exists($rp->getName(), $args)) { + throw new UI\InvalidLinkException("Missing parameter \${$rp->getName()} required by " . Reflection::toString($rp->getDeclaringFunction())); + } + } + } + + // ADD ACTION & SIGNAL & FLASH + if ($action) { + $args[UI\Presenter::ActionKey] = $action; + } + + if (!empty($signal)) { + $args[UI\Presenter::SignalKey] = $component->getParameterId($signal); + $current = $current && $args[UI\Presenter::SignalKey] === $refPresenter->getParameter(UI\Presenter::SignalKey); + } + + if (($mode === 'redirect' || $mode === 'forward') && $refPresenter->hasFlashSession()) { + $flashKey = $refPresenter->getParameter(UI\Presenter::FlashKey); + $args[UI\Presenter::FlashKey] = is_string($flashKey) && $flashKey !== '' ? $flashKey : null; + } + + return $this->lastRequest = new Request($presenter, Request::FORWARD, $args, flags: ['current' => $current]); + } + + + /** + * Parse destination in format "[//] [[[module:]presenter:]action | signal! | this | @alias] [?query] [#fragment]" + * @throws UI\InvalidLinkException + * @return array{absolute: bool, path: string, signal: bool, args: ?array, fragment: string} + * @internal + */ + public static function parseDestination(string $destination): array + { + if (!preg_match('~^ (?//)?+ (?[^!?#]++) (?!)?+ (?\?[^#]*)?+ (?\#.*)?+ $~x', $destination, $matches)) { + throw new UI\InvalidLinkException("Invalid destination '$destination'."); + } + + if (!empty($matches['query'])) { + parse_str(substr($matches['query'], 1), $args); } - $params[UI\Presenter::PresenterKey] = $presenter; + return [ + 'absolute' => (bool) $matches['absolute'], + 'path' => $matches['path'], + 'signal' => !empty($matches['signal']), + 'args' => $args ?? null, + 'fragment' => $matches['fragment'] ?? '', + ]; + } - $url = $this->router->constructUrl($params, $this->refUrl); + + /** + * Converts Request to URL. + */ + public function requestToUrl(Request $request, ?bool $relative = false): string + { + $url = $this->router->constructUrl($request->toArray(), $this->refUrl); if ($url === null) { + $params = $request->getParameters(); unset($params[UI\Presenter::ActionKey], $params[UI\Presenter::PresenterKey]); - $paramsDecoded = urldecode(http_build_query($params, '', ', ')); - throw new UI\InvalidLinkException("No route for $dest($paramsDecoded)"); + $params = urldecode(http_build_query($params, '', ', ')); + throw new UI\InvalidLinkException("No route for {$request->getPresenterName()}:{$request->getParameter('action')}($params)"); + } + + if ($relative) { + $hostUrl = $this->refUrl->getHostUrl() . '/'; + if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) { + $url = substr($url, strlen($hostUrl) - 1); + } } - return $url . $frag; + return $url; } @@ -89,4 +287,11 @@ public function withReferenceUrl(string $url): static $this->presenterFactory, ); } + + + private function isDeprecated(?UI\Presenter $presenter, \ReflectionClass|\ReflectionMethod $reflection): bool + { + return $presenter?->invalidLinkMode + && (UI\ComponentReflection::parseAnnotation($reflection, 'deprecated') || $reflection->getAttributes(Attributes\Deprecated::class)); + } } diff --git a/site/vendor/nette/application/src/Application/MicroPresenter.php b/site/vendor/nette/application/src/Application/MicroPresenter.php index fcab61954..7ee8b0db4 100644 --- a/site/vendor/nette/application/src/Application/MicroPresenter.php +++ b/site/vendor/nette/application/src/Application/MicroPresenter.php @@ -78,7 +78,7 @@ public function run(Application\Request $request): Application\Response $params['presenter'] = $this; try { - $params = Application\UI\ComponentReflection::combineArgs($reflection, $params); + $params = Application\UI\ParameterConverter::toArguments($reflection, $params); } catch (Nette\InvalidArgumentException $e) { $this->error($e->getMessage()); } diff --git a/site/vendor/nette/application/src/Application/PresenterFactory.php b/site/vendor/nette/application/src/Application/PresenterFactory.php index 01875ae49..225f04768 100644 --- a/site/vendor/nette/application/src/Application/PresenterFactory.php +++ b/site/vendor/nette/application/src/Application/PresenterFactory.php @@ -23,6 +23,7 @@ class PresenterFactory implements IPresenterFactory 'Nette' => ['NetteModule\\', '*\\', '*Presenter'], ]; + private array $aliases = []; private array $cache = []; /** @var callable */ @@ -57,10 +58,6 @@ public function getPresenterClass(string &$name): string return $this->cache[$name]; } - if (!Nette\Utils\Strings::match($name, '#^[a-zA-Z\x7f-\xff][a-zA-Z0-9\x7f-\xff:]*$#D')) { - throw new InvalidPresenterException("Presenter name must be alphanumeric string, '$name' is invalid."); - } - $class = $this->formatPresenterClass($name); if (!class_exists($class)) { throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' was not found."); @@ -68,7 +65,6 @@ public function getPresenterClass(string &$name): string $reflection = new \ReflectionClass($class); $class = $reflection->getName(); - if (!$reflection->implementsInterface(IPresenter::class)) { throw new InvalidPresenterException("Cannot load presenter '$name', class '$class' is not Nette\\Application\\IPresenter implementor."); } elseif ($reflection->isAbstract()) { @@ -86,7 +82,7 @@ public function setMapping(array $mapping): static { foreach ($mapping as $module => $mask) { if (is_string($mask)) { - if (!preg_match('#^\\\\?([\w\\\\]*\\\\)?(\w*\*\w*?\\\\)?([\w\\\\]*\*\w*)$#D', $mask, $m)) { + if (!preg_match('#^\\\\?([\w\\\\]*\\\\)?(\w*\*\w*?\\\\)?([\w\\\\]*\*\*?\w*)$#D', $mask, $m)) { throw new Nette\InvalidStateException("Invalid mapping mask '$mask'."); } @@ -108,15 +104,34 @@ public function setMapping(array $mapping): static */ public function formatPresenterClass(string $presenter): string { + if (!Nette\Utils\Strings::match($presenter, '#^[a-zA-Z\x7f-\xff][a-zA-Z0-9\x7f-\xff:]*$#D')) { + throw new InvalidPresenterException("Presenter name must be alphanumeric string, '$presenter' is invalid."); + } $parts = explode(':', $presenter); $mapping = isset($parts[1], $this->mapping[$parts[0]]) ? $this->mapping[array_shift($parts)] : $this->mapping['*']; while ($part = array_shift($parts)) { - $mapping[0] .= str_replace('*', $part, $mapping[$parts ? 1 : 2]); + $mapping[0] .= strtr($mapping[$parts ? 1 : 2], ['**' => "$part\\$part", '*' => $part]); } return $mapping[0]; } + + + /** + * Sets pairs [alias => destination] + */ + public function setAliases(array $aliases): static + { + $this->aliases = $aliases; + return $this; + } + + + public function getAlias(string $alias): string + { + return $this->aliases[$alias] ?? throw new Nette\InvalidStateException("Link alias '$alias' was not found."); + } } diff --git a/site/vendor/nette/application/src/Application/UI/AccessPolicy.php b/site/vendor/nette/application/src/Application/UI/AccessPolicy.php new file mode 100644 index 000000000..7757cdd67 --- /dev/null +++ b/site/vendor/nette/application/src/Application/UI/AccessPolicy.php @@ -0,0 +1,125 @@ +getAttributes(); + $attrs = self::applyInternalRules($attrs); + foreach ($attrs as $attribute) { + $this->checkAttribute($attribute); + } + } + + + private function getAttributes(): array + { + return array_map( + fn($ra) => $ra->newInstance(), + $this->element->getAttributes(Attributes\Requires::class, \ReflectionAttribute::IS_INSTANCEOF), + ); + } + + + private function applyInternalRules(array $attrs): array + { + if ( + $this->element instanceof \ReflectionMethod + && str_starts_with($this->element->getName(), $this->component::formatSignalMethod('')) + && !ComponentReflection::parseAnnotation($this->element, 'crossOrigin') + && !Nette\Utils\Arrays::some($attrs, fn($attr) => $attr->sameOrigin === false) + ) { + $attrs[] = new Attributes\Requires(sameOrigin: true); + } + return $attrs; + } + + + private function checkAttribute(Attributes\Requires $attribute): void + { + $this->presenter ??= $this->component->getPresenterIfExists() ?? + throw new Nette\InvalidStateException('Presenter is required for checking requirements of ' . Reflection::toString($this->element)); + + if ($attribute->methods !== null) { + $this->checkHttpMethod($attribute); + } + + if ($attribute->actions !== null) { + $this->checkActions($attribute); + } + + if ($attribute->forward && !$this->presenter->isForwarded()) { + $this->presenter->error('Forwarded request is required by ' . Reflection::toString($this->element)); + } + + if ($attribute->sameOrigin && !$this->presenter->getHttpRequest()->isSameSite()) { + $this->presenter->detectedCsrf(); + } + + if ($attribute->ajax && !$this->presenter->getHttpRequest()->isAjax()) { + $this->presenter->error('AJAX request is required by ' . Reflection::toString($this->element), Nette\Http\IResponse::S403_Forbidden); + } + } + + + private function checkActions(Attributes\Requires $attribute): void + { + if ( + $this->element instanceof \ReflectionMethod + && !$this->element->getDeclaringClass()->isSubclassOf(Presenter::class) + ) { + throw new \LogicException('Requires(actions) used by ' . Reflection::toString($this->element) . ' is allowed only in presenter.'); + } + + if (!in_array($this->presenter->getAction(), $attribute->actions, strict: true)) { + $this->presenter->error("Action '{$this->presenter->getAction()}' is not allowed by " . Reflection::toString($this->element)); + } + } + + + private function checkHttpMethod(Attributes\Requires $attribute): void + { + if ($this->element instanceof \ReflectionClass) { + $this->presenter->allowedMethods = []; // bypass Presenter::checkHttpMethod() + } + + $allowed = array_map(strtoupper(...), $attribute->methods); + $method = $this->presenter->getHttpRequest()->getMethod(); + + if ($allowed !== ['*'] && !in_array($method, $allowed, strict: true)) { + $this->presenter->getHttpResponse()->setHeader('Allow', implode(',', $allowed)); + $this->presenter->error( + "Method $method is not allowed by " . Reflection::toString($this->element), + Nette\Http\IResponse::S405_MethodNotAllowed, + ); + } + } +} diff --git a/site/vendor/nette/application/src/Application/UI/Component.php b/site/vendor/nette/application/src/Application/UI/Component.php index 73111c884..a13a8e9de 100644 --- a/site/vendor/nette/application/src/Application/UI/Component.php +++ b/site/vendor/nette/application/src/Application/UI/Component.php @@ -73,6 +73,9 @@ public function getUniqueId(): string protected function createComponent(string $name): ?Nette\ComponentModel\IComponent { + if (method_exists($this, $method = 'createComponent' . $name)) { + (new AccessPolicy($this, new \ReflectionMethod($this, $method)))->checkAccess(); + } $res = parent::createComponent($name); if ($res && !$res instanceof SignalReceiver && !$res instanceof StatePersistent) { $type = $res::class; @@ -106,9 +109,10 @@ protected function tryCall(string $method, array $params): bool } $rm = $rc->getMethod($method); + (new AccessPolicy($this, $rm))->checkAccess(); $this->checkRequirements($rm); try { - $args = $rc->combineArgs($rm, $params); + $args = ParameterConverter::toArguments($rm, $params); } catch (Nette\InvalidArgumentException $e) { throw new Nette\Application\BadRequestException($e->getMessage()); } @@ -119,19 +123,11 @@ protected function tryCall(string $method, array $params): bool /** - * Checks for requirements such as authorization. + * Descendant can override this method to check for permissions. + * It is called with the presenter class and the render*(), action*(), and handle*() methods. */ - public function checkRequirements($element): void + public function checkRequirements(\ReflectionClass|\ReflectionMethod $element): void { - if ( - $element instanceof \ReflectionMethod - && str_starts_with($element->getName(), 'handle') - && !ComponentReflection::parseAnnotation($element, 'crossOrigin') - && !$element->getAttributes(Nette\Application\Attributes\CrossOrigin::class) - && !$this->getPresenter()->getHttpRequest()->isSameSite() - ) { - $this->getPresenter()->detectedCsrf(); - } } @@ -155,7 +151,7 @@ public function loadState(array $params): void $reflection = $this->getReflection(); foreach ($reflection->getParameters() as $name => $meta) { if (isset($params[$name])) { // nulls are ignored - if (!$reflection->convertType($params[$name], $meta['type'])) { + if (!ParameterConverter::convertType($params[$name], $meta['type'])) { throw new Nette\Application\BadRequestException(sprintf( "Value passed to persistent parameter '%s' in %s must be %s, %s given.", $name, @@ -180,7 +176,46 @@ public function loadState(array $params): void */ public function saveState(array &$params): void { - $this->getReflection()->saveState($this, $params); + $this->saveStatePartial($params, static::getReflection()); + } + + + /** + * @internal used by presenter + */ + public function saveStatePartial(array &$params, ComponentReflection $reflection): void + { + $tree = Nette\Application\Helpers::getClassesAndTraits(static::class); + + foreach ($reflection->getPersistentParams() as $name => $meta) { + if (isset($params[$name])) { + // injected value + + } elseif ( + array_key_exists($name, $params) // nulls are skipped + || (isset($meta['since']) && !isset($tree[$meta['since']])) // not related + || !isset($this->$name) + ) { + continue; + + } else { + $params[$name] = $this->$name; // object property value + } + + if (!ParameterConverter::convertType($params[$name], $meta['type'])) { + throw new InvalidLinkException(sprintf( + "Value passed to persistent parameter '%s' in %s must be %s, %s given.", + $name, + $this instanceof Presenter ? 'presenter ' . $this->getName() : "component '{$this->getUniqueId()}'", + $meta['type'], + get_debug_type($params[$name]), + )); + } + + if ($params[$name] === $meta['def'] || ($meta['def'] === null && $params[$name] === '')) { + $params[$name] = null; // value transmit is unnecessary + } + } } @@ -255,7 +290,7 @@ public function link(string $destination, $args = []): string $args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1); - return $this->getPresenter()->createRequest($this, $destination, $args, 'link'); + return $this->getPresenter()->getLinkGenerator()->link($destination, $args, $this, 'link'); } catch (InvalidLinkException $e) { return $this->getPresenter()->handleInvalidLink($e); @@ -279,7 +314,7 @@ public function lazyLink(string $destination, $args = []): Link /** * Determines whether it links to the current page. - * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" + * @param string $destination in format "[[[module:]presenter:]action | signal! | this]" * @param array|mixed $args * @throws InvalidLinkException */ @@ -289,7 +324,7 @@ public function isLinkCurrent(?string $destination = null, $args = []): bool $args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1); - $this->getPresenter()->createRequest($this, $destination, $args, 'test'); + $this->getPresenter()->getLinkGenerator()->createRequest($this, $destination, $args, 'test'); } return $this->getPresenter()->getLastCreatedRequestFlag('current'); @@ -309,7 +344,8 @@ public function redirect(string $destination, $args = []): void ? $args : array_slice(func_get_args(), 1); $presenter = $this->getPresenter(); - $presenter->redirectUrl($presenter->createRequest($this, $destination, $args, 'redirect')); + $presenter->saveGlobalState(); + $presenter->redirectUrl($presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect')); } @@ -327,7 +363,7 @@ public function redirectPermanent(string $destination, $args = []): void : array_slice(func_get_args(), 1); $presenter = $this->getPresenter(); $presenter->redirectUrl( - $presenter->createRequest($this, $destination, $args, 'redirect'), + $presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect'), Nette\Http\IResponse::S301_MovedPermanently, ); } diff --git a/site/vendor/nette/application/src/Application/UI/ComponentReflection.php b/site/vendor/nette/application/src/Application/UI/ComponentReflection.php index 0c7d8b004..75e5a2499 100644 --- a/site/vendor/nette/application/src/Application/UI/ComponentReflection.php +++ b/site/vendor/nette/application/src/Application/UI/ComponentReflection.php @@ -10,6 +10,8 @@ namespace Nette\Application\UI; use Nette; +use Nette\Application\Attributes; +use Nette\Utils\Reflection; /** @@ -22,11 +24,12 @@ final class ComponentReflection extends \ReflectionClass { private static array $ppCache = []; private static array $pcCache = []; - private static array $mcCache = []; + private static array $armCache = []; /** * Returns array of class properties that are public and have attribute #[Persistent] or #[Parameter] or annotation @persistent. + * @return array */ public function getParameters(): array { @@ -42,14 +45,14 @@ public function getParameters(): array continue; } elseif ( self::parseAnnotation($prop, 'persistent') - || $prop->getAttributes(Nette\Application\Attributes\Persistent::class) + || $prop->getAttributes(Attributes\Persistent::class) ) { $params[$prop->getName()] = [ 'def' => $prop->getDefaultValue(), - 'type' => self::getType($prop), - 'since' => $isPresenter ? Nette\Utils\Reflection::getPropertyDeclaringClass($prop)->getName() : null, + 'type' => ParameterConverter::getType($prop), + 'since' => $isPresenter ? Reflection::getPropertyDeclaringClass($prop)->getName() : null, ]; - } elseif ($prop->getAttributes(Nette\Application\Attributes\Parameter::class)) { + } elseif ($prop->getAttributes(Attributes\Parameter::class)) { $params[$prop->getName()] = [ 'type' => (string) ($prop->getType() ?? 'mixed'), ]; @@ -73,6 +76,7 @@ public function getParameters(): array /** * Returns array of persistent properties. They are public and have attribute #[Persistent] or annotation @persistent. + * @return array */ public function getPersistentParams(): array { @@ -80,6 +84,11 @@ public function getPersistentParams(): array } + /** + * Returns array of persistent components. They are tagged with class-level attribute + * #[Persistent] or annotation @persistent or returned by Presenter::getPersistentComponents(). + * @return array + */ public function getPersistentComponents(): array { $class = $this->getName(); @@ -88,12 +97,14 @@ public function getPersistentComponents(): array return $components; } - $components = []; - if ($this->isSubclassOf(Presenter::class)) { - foreach ($class::getPersistentComponents() as $name => $meta) { - $components[is_string($meta) ? $meta : $name] = ['since' => $class]; - } + $attrs = $this->getAttributes(Attributes\Persistent::class); + $names = $attrs + ? $attrs[0]->getArguments() + : (array) self::parseAnnotation($this, 'persistent'); + $names = array_merge($names, $class::getPersistentComponents()); + $components = array_fill_keys($names, ['since' => $class]); + if ($this->isSubclassOf(Presenter::class)) { $parent = new self($this->getParentClass()->getName()); $components = $parent->getPersistentComponents() + $components; } @@ -107,7 +118,7 @@ public function getPersistentComponents(): array */ public function saveState(Component $component, array &$params): void { - $tree = self::getClassesAndTraits($component::class); + $tree = Nette\Application\Helpers::getClassesAndTraits($component::class); foreach ($this->getPersistentParams() as $name => $meta) { if (isset($params[$name])) { @@ -124,7 +135,7 @@ public function saveState(Component $component, array &$params): void $params[$name] = $component->$name; // object property value } - if (!self::convertType($params[$name], $meta['type'])) { + if (!ParameterConverter::convertType($params[$name], $meta['type'])) { throw new InvalidLinkException(sprintf( "Value passed to persistent parameter '%s' in %s must be %s, %s given.", $name, @@ -147,105 +158,32 @@ public function saveState(Component $component, array &$params): void */ public function hasCallableMethod(string $method): bool { - $class = $this->getName(); - $cache = &self::$mcCache[strtolower($class . ':' . $method)]; - if ($cache === null) { - try { - $cache = false; - $rm = new \ReflectionMethod($class, $method); - $cache = $this->isInstantiable() && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic(); - } catch (\ReflectionException) { - } - } - - return $cache; + return $this->isInstantiable() + && $this->hasMethod($method) + && ($rm = $this->getMethod($method)) + && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic(); } - public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array - { - $res = []; - foreach ($method->getParameters() as $i => $param) { - $name = $param->getName(); - $type = self::getType($param); - if (isset($args[$name])) { - $res[$i] = $args[$name]; - if (!self::convertType($res[$i], $type)) { - throw new Nette\InvalidArgumentException(sprintf( - 'Argument $%s passed to %s() must be %s, %s given.', - $name, - ($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName(), - $type, - get_debug_type($args[$name]), - )); - } - } elseif ($param->isDefaultValueAvailable()) { - $res[$i] = $param->getDefaultValue(); - } elseif ($type === 'scalar' || $param->allowsNull()) { - $res[$i] = null; - } elseif ($type === 'array' || $type === 'iterable') { - $res[$i] = []; - } else { - throw new Nette\InvalidArgumentException(sprintf( - 'Missing parameter $%s required by %s()', - $name, - ($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName(), - )); - } - } - - return $res; - } - - - /** - * Lossless type conversion. - */ - public static function convertType(mixed &$val, string $types): bool + /** Returns action*() or render*() method if available */ + public function getActionRenderMethod(string $action): ?\ReflectionMethod { - $scalars = ['string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'true' => 1, 'false' => 1]; - $testable = ['iterable' => 1, 'object' => 1, 'array' => 1, 'null' => 1]; - - foreach (explode('|', ltrim($types, '?')) as $type) { - if (match (true) { - isset($scalars[$type]) => self::castScalar($val, $type), - isset($testable[$type]) => "is_$type"($val), - $type === 'scalar' => !is_array($val), // special type due to historical reasons - $type === 'mixed' => true, - $type === 'callable' => false, // intentionally disabled for security reasons - default => $val instanceof $type, - }) { - return true; - } - } - - return false; + $class = $this->name; + return self::$armCache[$class][$action] ??= + $this->hasCallableMethod($name = $class::formatActionMethod($action)) + || $this->hasCallableMethod($name = $class::formatRenderMethod($action)) + ? parent::getMethod($name) + : null; } - /** - * Lossless type casting. - */ - private static function castScalar(mixed &$val, string $type): bool + /** Returns handle*() method if available */ + public function getSignalMethod(string $signal): ?\ReflectionMethod { - if (!is_scalar($val)) { - return false; - } - - $tmp = ($val === false ? '0' : (string) $val); - if ($type === 'float') { - $tmp = preg_replace('#\.0*$#D', '', $tmp); - } - - $orig = $tmp; - $spec = ['true' => true, 'false' => false]; - isset($spec[$type]) ? $tmp = $spec[$type] : settype($tmp, $type); - if ($orig !== ($tmp === false ? '0' : (string) $tmp)) { - return false; // data-loss occurs - } - - $val = $tmp; - return true; + $class = $this->name; + return $this->hasCallableMethod($name = $class::formatSignalMethod($signal)) + ? parent::getMethod($name) + : null; } @@ -272,18 +210,6 @@ public static function parseAnnotation(\Reflector $ref, string $name): ?array } - public static function getType(\ReflectionParameter|\ReflectionProperty $item): string - { - if ($type = $item->getType()) { - return (string) $type; - } - $default = $item instanceof \ReflectionProperty || $item->isDefaultValueAvailable() - ? $item->getDefaultValue() - : null; - return $default === null ? 'scalar' : get_debug_type($default); - } - - /** * Has class specified annotation? */ @@ -322,23 +248,10 @@ public function getMethods($filter = -1): array } - /** - * return string[] - */ - public static function getClassesAndTraits(string $class): array + /** @deprecated */ + public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array { - $res = [$class => $class] + class_parents($class); - $addTraits = function (string $type) use (&$res, &$addTraits): void { - $res += class_uses($type); - foreach (class_uses($type) as $trait) { - $addTraits($trait); - } - }; - foreach ($res as $type) { - $addTraits($type); - } - - return $res; + return ParameterConverter::toArguments($method, $args); } } diff --git a/site/vendor/nette/application/src/Application/UI/Control.php b/site/vendor/nette/application/src/Application/UI/Control.php index 7c1141a09..a768fa550 100644 --- a/site/vendor/nette/application/src/Application/UI/Control.php +++ b/site/vendor/nette/application/src/Application/UI/Control.php @@ -90,7 +90,7 @@ public function templatePrepareFilters(Template $template): void /** * Saves the message to template, that can be displayed after redirect. */ - public function flashMessage(string|\stdClass|Nette\HtmlStringable $message, string $type = 'info'): \stdClass + public function flashMessage(string|\stdClass|\Stringable $message, string $type = 'info'): \stdClass { $id = $this->getParameterId('flash'); $flash = $message instanceof \stdClass ? $message : (object) [ diff --git a/site/vendor/nette/application/src/Application/UI/ParameterConverter.php b/site/vendor/nette/application/src/Application/UI/ParameterConverter.php new file mode 100644 index 000000000..063aea419 --- /dev/null +++ b/site/vendor/nette/application/src/Application/UI/ParameterConverter.php @@ -0,0 +1,190 @@ +getParameters() as $i => $param) { + $name = $param->getName(); + $type = self::getType($param); + if (isset($args[$name])) { + $res[$i] = $args[$name]; + if (!self::convertType($res[$i], $type)) { + throw new Nette\InvalidArgumentException(sprintf( + 'Argument $%s passed to %s must be %s, %s given.', + $name, + Reflection::toString($method), + $type, + get_debug_type($args[$name]), + )); + } + } elseif ($param->isDefaultValueAvailable()) { + $res[$i] = $param->getDefaultValue(); + } elseif ($type === 'scalar' || $param->allowsNull()) { + $res[$i] = null; + } elseif ($type === 'array' || $type === 'iterable') { + $res[$i] = []; + } else { + throw new Nette\InvalidArgumentException(sprintf( + 'Missing parameter $%s required by %s', + $name, + Reflection::toString($method), + )); + } + } + + return $res; + } + + + /** + * Converts list of arguments to named parameters & check types. + * @param \ReflectionParameter[] $missing arguments + * @throws InvalidLinkException + * @internal + */ + public static function toParameters( + \ReflectionMethod $method, + array &$args, + array $supplemental = [], + ?array &$missing = null, + ): void + { + $i = 0; + foreach ($method->getParameters() as $param) { + $type = self::getType($param); + $name = $param->getName(); + + if (array_key_exists($i, $args)) { + $args[$name] = $args[$i]; + unset($args[$i]); + $i++; + + } elseif (array_key_exists($name, $args)) { + // continue with process + + } elseif (array_key_exists($name, $supplemental)) { + $args[$name] = $supplemental[$name]; + } + + if (!isset($args[$name])) { + if ( + !$param->isDefaultValueAvailable() + && !$param->allowsNull() + && $type !== 'scalar' + && $type !== 'array' + && $type !== 'iterable' + ) { + $missing[] = $param; + unset($args[$name]); + } + + continue; + } + + if (!self::convertType($args[$name], $type)) { + throw new InvalidLinkException(sprintf( + 'Argument $%s passed to %s must be %s, %s given.', + $name, + Reflection::toString($method), + $type, + get_debug_type($args[$name]), + )); + } + + $def = $param->isDefaultValueAvailable() + ? $param->getDefaultValue() + : null; + if ($args[$name] === $def || ($def === null && $args[$name] === '')) { + $args[$name] = null; // value transmit is unnecessary + } + } + + if (array_key_exists($i, $args)) { + throw new InvalidLinkException('Passed more parameters than method ' . Reflection::toString($method) . ' expects.'); + } + } + + + /** + * Lossless type conversion. + */ + public static function convertType(mixed &$val, string $types): bool + { + $scalars = ['string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'true' => 1, 'false' => 1]; + $testable = ['iterable' => 1, 'object' => 1, 'array' => 1, 'null' => 1]; + + foreach (explode('|', ltrim($types, '?')) as $type) { + if (match (true) { + isset($scalars[$type]) => self::castScalar($val, $type), + isset($testable[$type]) => "is_$type"($val), + $type === 'scalar' => !is_array($val), // special type due to historical reasons + $type === 'mixed' => true, + $type === 'callable' => false, // intentionally disabled for security reasons + default => $val instanceof $type, + }) { + return true; + } + } + + return false; + } + + + /** + * Lossless type casting. + */ + private static function castScalar(mixed &$val, string $type): bool + { + if (!is_scalar($val)) { + return false; + } + + $tmp = ($val === false ? '0' : (string) $val); + if ($type === 'float') { + $tmp = preg_replace('#\.0*$#D', '', $tmp); + } + + $orig = $tmp; + $spec = ['true' => true, 'false' => false]; + isset($spec[$type]) ? $tmp = $spec[$type] : settype($tmp, $type); + if ($orig !== ($tmp === false ? '0' : (string) $tmp)) { + return false; // data-loss occurs + } + + $val = $tmp; + return true; + } + + + public static function getType(\ReflectionParameter|\ReflectionProperty $item): string + { + if ($type = $item->getType()) { + return (string) $type; + } + $default = $item instanceof \ReflectionProperty || $item->isDefaultValueAvailable() + ? $item->getDefaultValue() + : null; + return $default === null ? 'scalar' : get_debug_type($default); + } +} diff --git a/site/vendor/nette/application/src/Application/UI/Presenter.php b/site/vendor/nette/application/src/Application/UI/Presenter.php index 23f57f178..c6e60d3d9 100644 --- a/site/vendor/nette/application/src/Application/UI/Presenter.php +++ b/site/vendor/nette/application/src/Application/UI/Presenter.php @@ -12,6 +12,7 @@ use Nette; use Nette\Application; use Nette\Application\Helpers; +use Nette\Application\LinkGenerator; use Nette\Application\Responses; use Nette\Http; use Nette\Utils\Arrays; @@ -88,6 +89,8 @@ abstract class Presenter extends Control implements Application\IPresenter /** use absolute Urls or paths? */ public bool $absoluteUrls = false; + + /** @deprecated use #[Requires(methods: ...)] to specify allowed methods */ public array $allowedMethods = ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH']; private ?Nette\Application\Request $request = null; private ?Nette\Application\Response $response = null; @@ -96,22 +99,19 @@ abstract class Presenter extends Control implements Application\IPresenter private ?array $globalStateSinces; private string $action = ''; private string $view = ''; + private bool $forwarded = false; private string|bool $layout = ''; private \stdClass $payload; private string $signalReceiver; private ?string $signal = null; private bool $ajaxMode; private bool $startupCheck = false; - private ?Nette\Application\Request $lastCreatedRequest; - private ?array $lastCreatedRequestFlag; private readonly Nette\Http\IRequest $httpRequest; private readonly Nette\Http\IResponse $httpResponse; private readonly ?Nette\Http\Session $session; - private readonly ?Nette\Application\IPresenterFactory $presenterFactory; - private readonly ?Nette\Routing\Router $router; private readonly ?Nette\Security\User $user; private readonly ?TemplateFactory $templateFactory; - private Nette\Http\UrlScript $refUrlCache; + private readonly LinkGenerator $linkGenerator; public function __construct() @@ -163,6 +163,12 @@ public function isModuleCurrent(string $module): bool } + public function isForwarded(): bool + { + return $this->forwarded || $this->request->isMethod($this->request::FORWARD); + } + + /********************* interface IPresenter ****************d*g**/ @@ -178,9 +184,12 @@ public function run(Application\Request $request): Application\Response $this->initGlobalParameters(); try { - // STARTUP + // CHECK REQUIREMENTS + (new AccessPolicy($this, static::getReflection()))->checkAccess(); $this->checkRequirements(static::getReflection()); $this->checkHttpMethod(); + + // STARTUP Arrays::invoke($this->onStartup, $this); $this->startup(); if (!$this->startupCheck) { @@ -189,7 +198,14 @@ public function run(Application\Request $request): Application\Response } // calls $this->action() - $this->tryCall(static::formatActionMethod($this->action), $this->params); + try { + actionMethod: + $this->tryCall(static::formatActionMethod($this->action), $this->params); + } catch (Application\SwitchException $e) { + $this->changeAction($e->getMessage()); + $this->autoCanonicalize = false; + goto actionMethod; + } // autoload components foreach ($this->globalParams as $id => $foo) { @@ -212,12 +228,20 @@ public function run(Application\Request $request): Application\Response $this->beforeRender(); Arrays::invoke($this->onRender, $this); // calls $this->render() - $this->tryCall(static::formatRenderMethod($this->view), $this->params); + try { + renderMethod: + $this->tryCall(static::formatRenderMethod($this->view), $this->params); + } catch (Application\SwitchException $e) { + $this->setView($e->getMessage()); + goto renderMethod; + } $this->afterRender(); // finish template rendering $this->sendTemplate(); + } catch (Application\SwitchException $e) { + throw new \LogicException('Switch is only allowed inside action*() or render*() method.', 0, $e); } catch (Application\AbortException) { } @@ -295,6 +319,7 @@ public function detectedCsrf(): void } + /** @deprecated use #[Requires(methods: ...)] to specify allowed methods */ protected function checkHttpMethod(): void { if ($this->allowedMethods && @@ -390,10 +415,20 @@ final public function getAction(bool $fullyQualified = false): string */ public function changeAction(string $action): void { + $this->forwarded = true; $this->action = $this->view = $action; } + /** + * Switch from current action or render method to another. + */ + public function switch(string $action): never + { + throw new Application\SwitchException($action); + } + + /** * Returns current view. */ @@ -408,6 +443,7 @@ final public function getView(): string */ public function setView(string $view): static { + $this->forwarded = true; $this->view = $view; return $this; } @@ -491,6 +527,7 @@ public function findLayoutTemplateFile(): ?string /** * Formats layout template file names. + * @return string[] */ public function formatLayoutTemplateFiles(): array { @@ -498,18 +535,28 @@ public function formatLayoutTemplateFiles(): array return [$this->layout]; } - [$module, $presenter] = Helpers::splitName($this->getName()); $layout = $this->layout ?: 'layout'; $dir = dirname(static::getReflection()->getFileName()); - $dir = is_dir("$dir/templates") ? $dir : dirname($dir); + $levels = substr_count($this->getName(), ':'); + if (!is_dir("$dir/templates")) { + $dir = dirname($origDir = $dir); + if (!is_dir("$dir/templates")) { + $list = ["$origDir/@$layout.latte"]; + do { + $list[] = "$dir/@$layout.latte"; + } while ($levels-- && ($dir = dirname($dir))); + return $list; + } + } + + [, $presenter] = Helpers::splitName($this->getName()); $list = [ "$dir/templates/$presenter/@$layout.latte", "$dir/templates/$presenter.@$layout.latte", ]; do { $list[] = "$dir/templates/@$layout.latte"; - $dir = dirname($dir); - } while ($dir && $module && ([$module] = Helpers::splitName($module))); + } while ($levels-- && ($dir = dirname($dir))); return $list; } @@ -517,12 +564,21 @@ public function formatLayoutTemplateFiles(): array /** * Formats view template file names. + * @return string[] */ public function formatTemplateFiles(): array { - [, $presenter] = Helpers::splitName($this->getName()); $dir = dirname(static::getReflection()->getFileName()); - $dir = is_dir("$dir/templates") ? $dir : dirname($dir); + if (!is_dir("$dir/templates")) { + $dir = dirname($origDir = $dir); + if (!is_dir("$dir/templates")) { + return [ + "$origDir/$this->view.latte", + ]; + } + } + + [, $presenter] = Helpers::splitName($this->getName()); return [ "$dir/templates/$presenter/$this->view.latte", "$dir/templates/$presenter.$this->view.latte", @@ -535,7 +591,7 @@ public function formatTemplateFiles(): array */ public static function formatActionMethod(string $action): string { - return 'action' . $action; + return 'action' . ucfirst($action); } @@ -544,7 +600,7 @@ public static function formatActionMethod(string $action): string */ public static function formatRenderMethod(string $view): string { - return 'render' . $view; + return 'render' . ucfirst($view); } @@ -648,8 +704,8 @@ public function forward(string|Nette\Application\Request $destination, $args = [ $args = func_num_args() < 3 && is_array($args) ? $args : array_slice(func_get_args(), 1); - $this->createRequest($this, $destination, $args, 'forward'); - $this->sendResponse(new Responses\ForwardResponse($this->lastCreatedRequest)); + $request = $this->linkGenerator->createRequest($this, $destination, $args, 'forward'); + $this->sendResponse(new Responses\ForwardResponse($request)); } @@ -680,7 +736,7 @@ public function redirectUrl(string $url, ?int $httpCode = null): void */ final public function getLastCreatedRequest(): ?Application\Request { - return $this->lastCreatedRequest; + return $this->linkGenerator->lastRequest; } @@ -690,7 +746,7 @@ final public function getLastCreatedRequest(): ?Application\Request */ final public function getLastCreatedRequestFlag(string $flag): bool { - return !empty($this->lastCreatedRequestFlag[$flag]); + return (bool) $this->linkGenerator->lastRequest?->hasFlag($flag); } @@ -710,10 +766,10 @@ public function canonicalize(?string $destination = null, ...$args): void ? $args[0] : $args; try { - $url = $this->createRequest( - $this, + $url = $this->linkGenerator->link( $destination ?: $this->action, $args + $this->getGlobalState() + $request->getParameters(), + $this, 'redirectX', ); } catch (InvalidLinkException) { @@ -753,329 +809,24 @@ public function lastModified( } - /** - * Request/URL factory. - * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" - * @param string $mode forward|redirect|link - * @throws InvalidLinkException - * @internal - */ - protected function createRequest( - Component $component, - string $destination, - array $args, - string $mode, - ): ?string + /** @deprecated @internal */ + protected function createRequest(Component $component, string $destination, array $args, string $mode): ?string { - // note: createRequest supposes that saveState(), run() & tryCall() behaviour is final - - $this->lastCreatedRequest = $this->lastCreatedRequestFlag = null; - - $parts = static::parseDestination($destination); - $path = $parts['path']; - $args = $parts['args'] ?? $args; - - if (!$component instanceof self || $parts['signal']) { - [$cname, $signal] = Helpers::splitName($path); - if ($cname !== '') { - $component = $component->getComponent(strtr($cname, ':', '-')); - } - - if ($signal === '') { - throw new InvalidLinkException('Signal must be non-empty string.'); - } - - $path = 'this'; - } - - $current = false; - [$presenter, $action] = Helpers::splitName($path); - if ($presenter === '') { - $action = $path === 'this' ? $this->action : $action; - $presenter = $this->getName(); - $presenterClass = static::class; - - } else { - if ($presenter[0] === ':') { // absolute - $presenter = substr($presenter, 1); - if (!$presenter) { - throw new InvalidLinkException("Missing presenter name in '$destination'."); - } - } else { // relative - [$module, , $sep] = Helpers::splitName($this->getName()); - $presenter = $module . $sep . $presenter; - } - - if (empty($this->presenterFactory)) { - throw new Nette\InvalidStateException('Unable to create link to other presenter, service PresenterFactory has not been set.'); - } - - try { - $presenterClass = $this->presenterFactory->getPresenterClass($presenter); - } catch (Application\InvalidPresenterException $e) { - throw new InvalidLinkException($e->getMessage(), 0, $e); - } - } - - // PROCESS SIGNAL ARGUMENTS - if (isset($signal)) { // $component must be StatePersistent - $reflection = new ComponentReflection($component::class); - if ($signal === 'this') { // means "no signal" - $signal = ''; - if (array_key_exists(0, $args)) { - throw new InvalidLinkException("Unable to pass parameters to 'this!' signal."); - } - } elseif (!str_contains($signal, self::NameSeparator)) { - // counterpart of signalReceived() & tryCall() - $method = $component->formatSignalMethod($signal); - if (!$reflection->hasCallableMethod($method)) { - throw new InvalidLinkException("Unknown signal '$signal', missing handler {$reflection->getName()}::$method()"); - } - - if ( - $this->invalidLinkMode - && ComponentReflection::parseAnnotation(new \ReflectionMethod($component, $method), 'deprecated') - ) { - trigger_error("Link to deprecated signal '$signal'" . ($component === $this ? '' : ' in ' . $component::class) . " from '{$this->getName()}:{$this->getAction()}'.", E_USER_DEPRECATED); - } - - // convert indexed parameters to named - static::argsToParams($component::class, $method, $args, [], $missing); - } - - // counterpart of StatePersistent - if ($args && array_intersect_key($args, $reflection->getPersistentParams())) { - $component->saveState($args); - } - - if ($args && $component !== $this) { - $prefix = $component->getUniqueId() . self::NameSeparator; - foreach ($args as $key => $val) { - unset($args[$key]); - $args[$prefix . $key] = $val; - } - } - } - - // PROCESS ARGUMENTS - if (is_subclass_of($presenterClass, self::class)) { - if ($action === '') { - $action = self::DefaultAction; - } - - $current = ($action === '*' || strcasecmp($action, (string) $this->action) === 0) && $presenterClass === static::class; - - $reflection = new ComponentReflection($presenterClass); - if ($this->invalidLinkMode && ComponentReflection::parseAnnotation($reflection, 'deprecated')) { - trigger_error("Link to deprecated presenter '$presenter' from '{$this->getName()}:{$this->getAction()}'.", E_USER_DEPRECATED); - } - - // counterpart of run() & tryCall() - $method = $presenterClass::formatActionMethod($action); - if (!$reflection->hasCallableMethod($method)) { - $method = $presenterClass::formatRenderMethod($action); - if (!$reflection->hasCallableMethod($method)) { - $method = null; - } - } - - // convert indexed parameters to named - if ($method === null) { - if (array_key_exists(0, $args)) { - throw new InvalidLinkException("Unable to pass parameters to action '$presenter:$action', missing corresponding method."); - } - } else { - if ( - $this->invalidLinkMode - && ComponentReflection::parseAnnotation(new \ReflectionMethod($presenterClass, $method), 'deprecated') - ) { - trigger_error("Link to deprecated action '$presenter:$action' from '{$this->getName()}:{$this->getAction()}'.", E_USER_DEPRECATED); - } - - static::argsToParams($presenterClass, $method, $args, $path === 'this' ? $this->params : [], $missing); - } - - // counterpart of StatePersistent - if ($args && array_intersect_key($args, $reflection->getPersistentParams())) { - $this->saveState($args, $reflection); - } - - if ($mode === 'redirect') { - $this->saveGlobalState(); - } - - $globalState = $this->getGlobalState($path === 'this' ? null : $presenterClass); - if ($current && $args) { - $tmp = $globalState + $this->params; - foreach ($args as $key => $val) { - if (http_build_query([$val]) !== (isset($tmp[$key]) ? http_build_query([$tmp[$key]]) : '')) { - $current = false; - break; - } - } - } - - $args += $globalState; - } - - if ($mode !== 'test' && !empty($missing)) { - foreach ($missing as $rp) { - if (!array_key_exists($rp->getName(), $args)) { - throw new InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()"); - } - } - } - - // ADD ACTION & SIGNAL & FLASH - if ($action) { - $args[self::ActionKey] = $action; - } - - if (!empty($signal)) { - $args[self::SignalKey] = $component->getParameterId($signal); - $current = $current && $args[self::SignalKey] === $this->getParameter(self::SignalKey); - } - - if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) { - $args[self::FlashKey] = $this->getFlashKey(); - } - - $this->lastCreatedRequest = new Application\Request($presenter, Application\Request::FORWARD, $args); - $this->lastCreatedRequestFlag = ['current' => $current]; - - return $mode === 'forward' || $mode === 'test' - ? null - : $this->requestToUrl($this->lastCreatedRequest, $mode === 'link' && !$parts['absolute'] && !$this->absoluteUrls) . $parts['fragment']; + return $this->linkGenerator->link($destination, $args, $component, $mode); } - /** - * Parse destination in format "[//] [[[module:]presenter:]action | signal! | this] [?query] [#fragment]" - * @throws InvalidLinkException - * @internal - */ + /** @deprecated @internal */ public static function parseDestination(string $destination): array { - if (!preg_match('~^ (?//)?+ (?[^!?#]++) (?!)?+ (?\?[^#]*)?+ (?\#.*)?+ $~x', $destination, $matches)) { - throw new InvalidLinkException("Invalid destination '$destination'."); - } - - if (!empty($matches['query'])) { - parse_str(substr($matches['query'], 1), $args); - } - - return [ - 'absolute' => (bool) $matches['absolute'], - 'path' => $matches['path'], - 'signal' => !empty($matches['signal']), - 'args' => $args ?? null, - 'fragment' => $matches['fragment'] ?? '', - ]; + return LinkGenerator::parseDestination($destination); } - /** - * Converts Request to URL. - * @internal - */ + /** @deprecated @internal */ protected function requestToUrl(Application\Request $request, ?bool $relative = null): string { - if (!isset($this->refUrlCache)) { - $url = $this->httpRequest->getUrl(); - $this->refUrlCache = new Http\UrlScript($url->getHostUrl() . $url->getScriptPath()); - } - - if (empty($this->router)) { - throw new Nette\InvalidStateException('Unable to generate URL, service Router has not been set.'); - } - - $url = $this->router->constructUrl($request->toArray(), $this->refUrlCache); - if ($url === null) { - $params = $request->getParameters(); - unset($params[self::ActionKey], $params[self::PresenterKey]); - $params = urldecode(http_build_query($params, '', ', ')); - throw new InvalidLinkException("No route for {$request->getPresenterName()}:{$request->getParameter('action')}($params)"); - } - - if ($relative ?? !$this->absoluteUrls) { - $hostUrl = $this->refUrlCache->getHostUrl() . '/'; - if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) { - $url = substr($url, strlen($hostUrl) - 1); - } - } - - return $url; - } - - - /** - * Converts list of arguments to named parameters. - * @param \ReflectionParameter[] $missing arguments - * @throws InvalidLinkException - * @internal - */ - public static function argsToParams( - string $class, - string $method, - array &$args, - array $supplemental = [], - ?array &$missing = null, - ): void - { - $i = 0; - $rm = new \ReflectionMethod($class, $method); - foreach ($rm->getParameters() as $param) { - $type = ComponentReflection::getType($param); - $name = $param->getName(); - - if (array_key_exists($i, $args)) { - $args[$name] = $args[$i]; - unset($args[$i]); - $i++; - - } elseif (array_key_exists($name, $args)) { - // continue with process - - } elseif (array_key_exists($name, $supplemental)) { - $args[$name] = $supplemental[$name]; - } - - if (!isset($args[$name])) { - if ( - !$param->isDefaultValueAvailable() - && !$param->allowsNull() - && $type !== 'scalar' - && $type !== 'array' - && $type !== 'iterable' - ) { - $missing[] = $param; - unset($args[$name]); - } - - continue; - } - - if (!ComponentReflection::convertType($args[$name], $type)) { - throw new InvalidLinkException(sprintf( - 'Argument $%s passed to %s() must be %s, %s given.', - $name, - $rm->getDeclaringClass()->getName() . '::' . $rm->getName(), - $type, - get_debug_type($args[$name]), - )); - } - - $def = $param->isDefaultValueAvailable() - ? $param->getDefaultValue() - : null; - if ($args[$name] === $def || ($def === null && $args[$name] === '')) { - $args[$name] = null; // value transmit is unnecessary - } - } - - if (array_key_exists($i, $args)) { - throw new InvalidLinkException("Passed more parameters than method $class::{$rm->getName()}() expects."); - } + return $this->linkGenerator->requestToUrl($request, $relative ?? !$this->absoluteUrls); } @@ -1135,7 +886,7 @@ public function restoreRequest(string $key): void $request->setFlag(Application\Request::RESTORED, true); $this->sendResponse(new Responses\ForwardResponse($request)); } else { - $this->redirectUrl($this->requestToUrl($request)); + $this->redirectUrl($this->linkGenerator->requestToUrl($request)); } } @@ -1144,23 +895,19 @@ public function restoreRequest(string $key): void /** - * Returns array of persistent components. - * This default implementation detects components by class-level annotation @persistent(cmp1, cmp2). + * Descendant can override this method to return the names of custom persistent components. + * @return string[] */ public static function getPersistentComponents(): array { - $rc = new \ReflectionClass(static::class); - $attrs = $rc->getAttributes(Application\Attributes\Persistent::class); - return $attrs - ? $attrs[0]->getArguments() - : (array) ComponentReflection::parseAnnotation($rc, 'persistent'); + return []; } /** * Saves state information for all subcomponents to $this->globalState. */ - protected function getGlobalState(?string $forClass = null): array + public function getGlobalState(?string $forClass = null): array { $sinces = &$this->globalStateSinces; @@ -1173,7 +920,7 @@ protected function getGlobalState(?string $forClass = null): array } } - $this->saveState($state, $forClass ? new ComponentReflection($forClass) : null); + $this->saveStatePartial($state, new ComponentReflection($forClass ?? $this)); if ($sinces === null) { $sinces = []; @@ -1207,7 +954,7 @@ protected function getGlobalState(?string $forClass = null): array } if ($forClass !== null) { - $tree = ComponentReflection::getClassesAndTraits($forClass); + $tree = Helpers::getClassesAndTraits($forClass); $since = null; foreach ($state as $key => $foo) { if (!isset($sinces[$key])) { @@ -1231,15 +978,6 @@ protected function getGlobalState(?string $forClass = null): array } - /** - * Saves state information for next request. - */ - public function saveState(array &$params, ?ComponentReflection $reflection = null): void - { - ($reflection ?: static::getReflection())->saveState($this, $params); - } - - /** * Permanently saves state information for all subcomponents to $this->globalState. */ @@ -1287,6 +1025,7 @@ private function initGlobalParameters(): void } $this->changeAction($action); + $this->forwarded = false; // init $this->signalReceiver and key 'signal' in appropriate params array $this->signalReceiver = $this->getUniqueId(); @@ -1376,13 +1115,19 @@ final public function injectPrimary( ?TemplateFactory $templateFactory = null, ): void { - $this->presenterFactory = $presenterFactory; - $this->router = $router; $this->httpRequest = $httpRequest; $this->httpResponse = $httpResponse; $this->session = $session; $this->user = $user; $this->templateFactory = $templateFactory; + if ($router && $presenterFactory) { + $url = $httpRequest->getUrl(); + $this->linkGenerator = new LinkGenerator( + $router, + new Http\UrlScript($url->getHostUrl() . $url->getScriptPath()), + $presenterFactory, + ); + } } @@ -1420,4 +1165,10 @@ final public function getTemplateFactory(): TemplateFactory { return $this->templateFactory ?? throw new Nette\InvalidStateException('Service TemplateFactory has not been set.'); } + + + final protected function getLinkGenerator(): LinkGenerator + { + return $this->linkGenerator ?? throw new Nette\InvalidStateException('Unable to create link to other presenter, service PresenterFactory or Router has not been set.'); + } } diff --git a/site/vendor/nette/application/src/Application/exceptions.php b/site/vendor/nette/application/src/Application/exceptions.php index a23d83a10..11aea987d 100644 --- a/site/vendor/nette/application/src/Application/exceptions.php +++ b/site/vendor/nette/application/src/Application/exceptions.php @@ -20,6 +20,11 @@ class AbortException extends \LogicException { } +/** @internal */ +final class SwitchException extends AbortException +{ +} + /** * Application fatal error. diff --git a/site/vendor/nette/application/src/Bridges/ApplicationDI/ApplicationExtension.php b/site/vendor/nette/application/src/Bridges/ApplicationDI/ApplicationExtension.php index fcf8ce463..a874f55f4 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationDI/ApplicationExtension.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationDI/ApplicationExtension.php @@ -47,11 +47,12 @@ public function getConfigSchema(): Nette\Schema\Schema ])->castTo('array'), Expect::string()->dynamic(), )->firstIsDefault(), - 'catchExceptions' => Expect::anyOf('4xx', true, false)->firstIsDefault()->dynamic(), + 'catchExceptions' => Expect::bool(false)->dynamic(), 'mapping' => Expect::anyOf( Expect::string(), Expect::arrayOf('string|array'), ), + 'aliases' => Expect::arrayOf('string'), 'scanDirs' => Expect::anyOf( Expect::arrayOf('string')->default($this->scanDirs)->mergeDefaults(), false, @@ -77,8 +78,6 @@ public function loadConfiguration(): void ->setFactory(Nette\Application\Application::class); if ($config->catchExceptions || !$this->debugMode) { $application->addSetup('$error4xxPresenter', [is_array($config->errorPresenter) ? $config->errorPresenter['4xx'] : $config->errorPresenter]); - } - if ($config->catchExceptions === true || !$this->debugMode) { $application->addSetup('$errorPresenter', [is_array($config->errorPresenter) ? $config->errorPresenter['5xx'] : $config->errorPresenter]); } @@ -103,6 +102,10 @@ public function loadConfiguration(): void ]); } + if ($config->aliases) { + $presenterFactory->addSetup('setAliases', [$config->aliases]); + } + $builder->addDefinition($this->prefix('linkGenerator')) ->setFactory(Nette\Application\LinkGenerator::class, [ 1 => new Definitions\Statement([new Definitions\Statement('@Nette\Http\IRequest::getUrl'), 'withoutUserInfo']), @@ -151,6 +154,7 @@ public function beforeCompile(): void } + /** @return string[] */ private function findPresenters(): array { $config = $this->getConfig(); diff --git a/site/vendor/nette/application/src/Bridges/ApplicationDI/LatteExtension.php b/site/vendor/nette/application/src/Bridges/ApplicationDI/LatteExtension.php index 6fffe737a..736dbd57a 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationDI/LatteExtension.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationDI/LatteExtension.php @@ -12,6 +12,7 @@ use Latte; use Nette; use Nette\Bridges\ApplicationLatte; +use Nette\DI\Definitions\Statement; use Nette\Schema\Expect; use Tracy; @@ -67,6 +68,17 @@ public function loadConfiguration(): void $latteFactory->addSetup('setStrictParsing', [$config->strictParsing]) ->addSetup('enablePhpLinter', [$config->phpLinter]); + $builder->getDefinition($this->prefix('latteFactory')) + ->getResultDefinition() + ->addSetup('?', [$builder::literal('func_num_args() && $service->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension(func_get_arg(0)))')]); + + if ($cache = $builder->getByType(Nette\Caching\Storage::class)) { + $this->addExtension(new Statement(Nette\Bridges\CacheLatte\CacheExtension::class, [$builder->getDefinition($cache)])); + } + if (class_exists(Nette\Bridges\FormsLatte\FormsExtension::class)) { + $this->addExtension(new Statement(Nette\Bridges\FormsLatte\FormsExtension::class)); + } + foreach ($config->extensions as $extension) { $this->addExtension($extension); } @@ -146,10 +158,10 @@ public function addMacro(string $macro): void } - public function addExtension(Nette\DI\Definitions\Statement|string $extension): void + public function addExtension(Statement|string $extension): void { $extension = is_string($extension) - ? new Nette\DI\Definitions\Statement($extension) + ? new Statement($extension) : $extension; $builder = $this->getContainerBuilder(); diff --git a/site/vendor/nette/application/src/Bridges/ApplicationLatte/LatteFactory.php b/site/vendor/nette/application/src/Bridges/ApplicationLatte/LatteFactory.php index a215f3261..e6d2c8842 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationLatte/LatteFactory.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationLatte/LatteFactory.php @@ -14,7 +14,7 @@ interface LatteFactory { - function create(): Latte\Engine; + function create(/*?Control $control = null*/): Latte\Engine; } diff --git a/site/vendor/nette/application/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php b/site/vendor/nette/application/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php index a3421d976..151913a07 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php @@ -12,10 +12,8 @@ use Latte; use Latte\Compiler\PhpHelpers; use Latte\Compiler\PrintContext; -use Nette\Application\UI\Presenter; +use Nette\Application\UI; use Nette\Bridges\ApplicationLatte\Template; -use Nette\PhpGenerator as Php; - /** * {templatePrint [ClassName]} @@ -24,42 +22,40 @@ class TemplatePrintNode extends Latte\Essential\Nodes\TemplatePrintNode { public function print(PrintContext $context): string { - return self::class . '::printClass($this, ' . PhpHelpers::dump($this->template) . '); exit;'; + return self::class . '::printClass($this->getParameters(), ' . PhpHelpers::dump($this->template ?? Template::class) . '); exit;'; } - public static function printClass(Latte\Runtime\Template $template, ?string $parent = null): void + public static function printClass(array $params, string $parentClass): void { - $blueprint = new Latte\Essential\Blueprint; - $name = 'Template'; - $params = $template->getParameters(); + $bp = new Latte\Essential\Blueprint; + if (!method_exists($bp, 'generateTemplateClass')) { + throw new \LogicException("Please update 'latte/latte' to version 3.0.15 or newer."); + } + $control = $params['control'] ?? $params['presenter'] ?? null; - if ($control) { + $name = 'Template'; + if ($control instanceof UI\Control) { $name = preg_replace('#(Control|Presenter)$#', '', $control::class) . 'Template'; - unset($params[$control instanceof Presenter ? 'control' : 'presenter']); + unset($params[$control instanceof UI\Presenter ? 'control' : 'presenter']); } - - if ($parent) { - if (!class_exists($parent)) { - $blueprint->printHeader("{templatePrint}: Class '$parent' doesn't exist."); - return; + $class = $bp->generateTemplateClass($params, $name, $parentClass); + $code = (string) $class->getNamespace(); + + $bp->printBegin(); + $bp->printCode($code); + + if ($control instanceof UI\Control) { + $file = dirname((new \ReflectionClass($control))->getFileName()) . '/' . $class->getName() . '.php'; + if (file_exists($file)) { + echo "unsaved, file {$bp->clickableFile($file)} already exists"; + } else { + echo "saved to file {$bp->clickableFile($file)}"; + file_put_contents($file, "global->fn, (new Latte\Essential\CoreExtension)->getFunctions()); - unset($funcs['isLinkCurrent'], $funcs['isModuleCurrent']); - - $namespace = new Php\PhpNamespace(Php\Helpers::extractNamespace($name)); - $class = $namespace->addClass(Php\Helpers::extractShortName($name)); - $class->setExtends($parent ?: Template::class); - - $blueprint->addProperties($class, $params); - $blueprint->addFunctions($class, $funcs); - - $end = $blueprint->printCanvas(); - $blueprint->printCode((string) $namespace); - echo $end; + $bp->printEnd(); + exit; } } diff --git a/site/vendor/nette/application/src/Bridges/ApplicationLatte/Template.php b/site/vendor/nette/application/src/Bridges/ApplicationLatte/Template.php index 4912b27c2..c07ea9975 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationLatte/Template.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationLatte/Template.php @@ -19,6 +19,7 @@ class Template implements Nette\Application\UI\Template { private ?string $file = null; + private ?string $blueprint; public function __construct( @@ -39,6 +40,9 @@ final public function getLatte(): Latte\Engine public function render(?string $file = null, array $params = []): void { Nette\Utils\Arrays::toObject($params, $this); + if (isset($this->blueprint)) { + Nodes\TemplatePrintNode::printClass($this->getParameters(), $this->blueprint); + } $this->latte->render($file ?: $this->file, $this); } @@ -139,6 +143,12 @@ final public function getParameters(): array } + public function blueprint(?string $parentClass = null): void + { + $this->blueprint = $parentClass ?? self::class; + } + + /** * Prevents unserialization. */ diff --git a/site/vendor/nette/application/src/Bridges/ApplicationLatte/TemplateFactory.php b/site/vendor/nette/application/src/Bridges/ApplicationLatte/TemplateFactory.php index bc7b0c042..2fdcbc9b4 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationLatte/TemplateFactory.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationLatte/TemplateFactory.php @@ -47,33 +47,14 @@ public function createTemplate(?UI\Control $control = null, ?string $class = nul throw new Nette\InvalidArgumentException("Class $class does not implement " . Template::class . ' or it does not exist.'); } - $latte = $this->latteFactory->create(); + $latte = $this->latteFactory->create($control); $template = new $class($latte); $presenter = $control?->getPresenterIfExists(); if (version_compare(Latte\Engine::VERSION, '3', '<')) { $this->setupLatte2($latte, $control, $presenter, $template); - - } else { + } elseif (!Nette\Utils\Arrays::some($latte->getExtensions(), fn($e) => $e instanceof UIExtension)) { $latte->addExtension(new UIExtension($control)); - - if ($this->cacheStorage && class_exists(Nette\Bridges\CacheLatte\CacheExtension::class)) { - $latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($this->cacheStorage)); - } - - if (class_exists(Nette\Bridges\FormsLatte\FormsExtension::class)) { - $latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension); - } - } - - $latte->addFilter('modifyDate', fn($time, $delta, $unit = null) => $time - ? Nette\Utils\DateTime::from($time)->modify($delta . $unit) - : null); - - if (!isset($latte->getFilters()['translate'])) { - $latte->addFilter('translate', function (Latte\Runtime\FilterInfo $fi): void { - throw new Nette\InvalidStateException('Translator has not been set. Set translator using $template->setTranslator().'); - }); } // default parameters @@ -148,5 +129,10 @@ private function setupLatte2( $latte->addFunction('isLinkCurrent', [$presenter, 'isLinkCurrent']); $latte->addFunction('isModuleCurrent', [$presenter, 'isModuleCurrent']); } + + $latte->addFilter('modifyDate', fn($time, $delta, $unit = null) => $time + ? Nette\Utils\DateTime::from($time)->modify($delta . $unit) + : null); + } } diff --git a/site/vendor/nette/application/src/Bridges/ApplicationLatte/UIExtension.php b/site/vendor/nette/application/src/Bridges/ApplicationLatte/UIExtension.php index 2071a69fd..bc18af2bc 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationLatte/UIExtension.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationLatte/UIExtension.php @@ -29,6 +29,16 @@ public function __construct( } + public function getFilters(): array + { + return [ + 'modifyDate' => fn($time, $delta, $unit = null) => $time + ? Nette\Utils\DateTime::from($time)->modify($delta . $unit) + : null, + ]; + } + + public function getFunctions(): array { if ($presenter = $this->control?->getPresenterIfExists()) { diff --git a/site/vendor/nette/application/src/Bridges/ApplicationTracy/RoutingPanel.php b/site/vendor/nette/application/src/Bridges/ApplicationTracy/RoutingPanel.php index fa0209b46..3305c6cfb 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationTracy/RoutingPanel.php +++ b/site/vendor/nette/application/src/Bridges/ApplicationTracy/RoutingPanel.php @@ -116,33 +116,28 @@ private function analyse(Routing\RouteList $router, ?Nette\Http\IRequest $httpRe } - private function findSource(): \ReflectionClass|\ReflectionMethod|null + private function findSource(): \ReflectionClass|\ReflectionMethod|string|null { $params = $this->matched; $presenter = $params['presenter'] ?? ''; try { $class = $this->presenterFactory->getPresenterClass($presenter); } catch (Nette\Application\InvalidPresenterException) { + if ($this->presenterFactory instanceof Nette\Application\PresenterFactory) { + return $this->presenterFactory->formatPresenterClass($presenter); + } return null; } - $rc = new \ReflectionClass($class); - - if ($rc->isSubclassOf(Nette\Application\UI\Presenter::class)) { + if (is_a($class, Nette\Application\UI\Presenter::class, allow_string: true)) { + $rc = $class::getReflection(); if (isset($params[Presenter::SignalKey])) { - $method = $class::formatSignalMethod($params[Presenter::SignalKey]); - + return $rc->getSignalMethod($params[Presenter::SignalKey]); } elseif (isset($params[Presenter::ActionKey])) { - $action = $params[Presenter::ActionKey]; - $method = $class::formatActionMethod($action); - if (!$rc->hasMethod($method)) { - $method = $class::formatRenderMethod($action); - } + return $rc->getActionRenderMethod($params[Presenter::ActionKey]); } } - return isset($method) && $rc->hasMethod($method) - ? $rc->getMethod($method) - : $rc; + return new \ReflectionClass($class); } } diff --git a/site/vendor/nette/application/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml b/site/vendor/nette/application/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml index 2c62aada4..6d499b185 100644 --- a/site/vendor/nette/application/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml +++ b/site/vendor/nette/application/src/Bridges/ApplicationTracy/templates/RoutingPanel.panel.phtml @@ -100,8 +100,10 @@ use Tracy\Helpers;

getBaseUrl()) ?>&', '?'], Helpers::escapeHtml($url->getRelativeUrl())) ?>

- -

getName() : $source->getDeclaringClass()->getName() . '::' . $source->getName() . '()' ?>

+ +

(class not found)

+ +

getName() : $source->getDeclaringClass()->getName() . '::' . $source->getName() . '()' ?>

diff --git a/site/vendor/nette/bootstrap/config.stub.neon b/site/vendor/nette/bootstrap/config.stub.neon index dc5128106..056d123c8 100644 --- a/site/vendor/nette/bootstrap/config.stub.neon +++ b/site/vendor/nette/bootstrap/config.stub.neon @@ -4,6 +4,7 @@ parameters: wwwDir: absolute path to the directory containing the index.php entry file tempDir: absolute path to the directory for temporary files vendorDir: absolute path to the directory where Composer installs libraries + rootDir: absolute path to the root project directory debugMode: indicates whether the application is in debug mode productionMode: opposite of debugMode consoleMode: indicates whether the request came through the command line (CLI) diff --git a/site/vendor/nette/bootstrap/src/Bootstrap/Configurator.php b/site/vendor/nette/bootstrap/src/Bootstrap/Configurator.php index 704e20104..1b06830e7 100644 --- a/site/vendor/nette/bootstrap/src/Bootstrap/Configurator.php +++ b/site/vendor/nette/bootstrap/src/Bootstrap/Configurator.php @@ -173,6 +173,9 @@ protected function getDefaultParameters(): array 'appDir' => isset($trace[1]['file']) ? dirname($trace[1]['file']) : null, 'wwwDir' => isset($last['file']) ? dirname($last['file']) : null, 'vendorDir' => $loaderRc ? dirname($loaderRc->getFileName(), 2) : null, + 'rootDir' => class_exists(InstalledVersions::class) + ? rtrim(Nette\Utils\FileSystem::normalizePath(InstalledVersions::getRootPackage()['install_path']), '\\/') + : null, 'debugMode' => $debugMode, 'productionMode' => !$debugMode, 'consoleMode' => PHP_SAPI === 'cli', diff --git a/site/vendor/paragonie/halite/.github/workflows/ci.yml b/site/vendor/paragonie/halite/.github/workflows/ci.yml index 04dffdc19..9ca94603e 100644 --- a/site/vendor/paragonie/halite/.github/workflows/ci.yml +++ b/site/vendor/paragonie/halite/.github/workflows/ci.yml @@ -1,36 +1,34 @@ name: CI -on: [push, pull_request] +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: - modern: + phpunit: name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }} strategy: matrix: operating-system: ['ubuntu-latest'] - php-versions: ['8.1'] - phpunit-versions: ['latest'] + php-versions: ['8.1', '8.2', '8.3', '8.4'] + steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} extensions: mbstring, intl, sodium - ini-values: post_max_size=256M, max_execution_time=180 - tools: psalm, phpunit:${{ matrix.phpunit-versions }} + ini-values: error_reporting=-1, display_errors=On + coverage: none - - name: Install dependencies - run: composer install + - name: Install Composer dependencies + uses: "ramsey/composer-install@v2" - name: PHPUnit tests - uses: php-actions/phpunit@v2 - timeout-minutes: 30 - with: - memory_limit: 256M - - - name: Static Analysis - run: vendor/bin/psalm + run: vendor/bin/phpunit \ No newline at end of file diff --git a/site/vendor/paragonie/halite/.github/workflows/psalm.yml b/site/vendor/paragonie/halite/.github/workflows/psalm.yml new file mode 100644 index 000000000..f5bd5c58e --- /dev/null +++ b/site/vendor/paragonie/halite/.github/workflows/psalm.yml @@ -0,0 +1,34 @@ +name: Psalm + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + psalm: + name: Psalm on PHP ${{ matrix.php-versions }} + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: ['ubuntu-latest'] + php-versions: ['8.3'] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: psalm:4 + coverage: none + + - name: Install Composer dependencies + uses: "ramsey/composer-install@v2" + with: + composer-options: --no-dev + + - name: Static Analysis + run: psalm diff --git a/site/vendor/paragonie/halite/CHANGELOG.md b/site/vendor/paragonie/halite/CHANGELOG.md index acd5aa81f..b4a7cc401 100644 --- a/site/vendor/paragonie/halite/CHANGELOG.md +++ b/site/vendor/paragonie/halite/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog -## Version 5.1.0 (2202-05-23) +## Version 5.1.1 (2024-04-19) + +* Support both sodium_compat v1 and v2. + [Learn more here](https://paragonie.com/blog/2024/04/release-sodium-compat-v2-and-future-our-polyfill-libraries). + +## Version 5.1.0 (2022-05-23) * Dropped PHP 8.0 support, increased minimum PHP version to 8.1. * This is due to the significant performance difference between ext/sodium diff --git a/site/vendor/paragonie/halite/README.md b/site/vendor/paragonie/halite/README.md index 143543c48..78f991561 100644 --- a/site/vendor/paragonie/halite/README.md +++ b/site/vendor/paragonie/halite/README.md @@ -1,6 +1,7 @@ # Halite [![Build Status](https://github.com/paragonie/halite/actions/workflows/ci.yml/badge.svg)](https://github.com/paragonie/halite/actions) +[![Static Analysis](https://github.com/paragonie/halite/actions/workflows/psalm.yml/badge.svg)](https://github.com/paragonie/halite/actions) [![Latest Stable Version](https://poser.pugx.org/paragonie/halite/v/stable)](https://packagist.org/packages/paragonie/halite) [![Latest Unstable Version](https://poser.pugx.org/paragonie/halite/v/unstable)](https://packagist.org/packages/paragonie/halite) [![License](https://poser.pugx.org/paragonie/halite/license)](https://packagist.org/packages/paragonie/halite) diff --git a/site/vendor/paragonie/halite/composer.json b/site/vendor/paragonie/halite/composer.json index c32fea21b..e9937d53b 100644 --- a/site/vendor/paragonie/halite/composer.json +++ b/site/vendor/paragonie/halite/composer.json @@ -36,7 +36,7 @@ "ext-json": "*", "paragonie/constant_time_encoding": "^2", "paragonie/hidden-string": "^1|^2", - "paragonie/sodium_compat": "^1.17" + "paragonie/sodium_compat": "^1|^2" }, "autoload": { "psr-4": { diff --git a/site/vendor/paragonie/halite/psalm.xml b/site/vendor/paragonie/halite/psalm.xml index 69c5b3165..5d159e6a4 100644 --- a/site/vendor/paragonie/halite/psalm.xml +++ b/site/vendor/paragonie/halite/psalm.xml @@ -1,7 +1,7 @@ +> @@ -11,6 +11,7 @@ + diff --git a/site/vendor/phpstan/phpstan/phpstan.phar b/site/vendor/phpstan/phpstan/phpstan.phar index fd8f0c0d8..207b04ace 100755 Binary files a/site/vendor/phpstan/phpstan/phpstan.phar and b/site/vendor/phpstan/phpstan/phpstan.phar differ diff --git a/site/vendor/phpstan/phpstan/phpstan.phar.asc b/site/vendor/phpstan/phpstan/phpstan.phar.asc index 34ef3f53e..2e50bc434 100644 --- a/site/vendor/phpstan/phpstan/phpstan.phar.asc +++ b/site/vendor/phpstan/phpstan/phpstan.phar.asc @@ -1,16 +1,16 @@ -----BEGIN PGP SIGNATURE----- -iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmYFl+cACgkQUcZzBf/C -5cAS1g//S99FoemVRWTmzdRPtlwFVnYom0MA1cx6Wgz6d3Tjj2AGKl3o/mY6+V5+ -Gqm0LEwuMlBHmHcBG/ZajpcDy0s5eBNY0t7wNCL2juXz9I89z8TJC6ypYVLFqKRR -iNqCeqR4i23ZTdqNWnvS8w3ZzrLa5z+mY9AEAz+mjK2k2fZjpAYP6YoFsNYYUQ+z -WaU0YgYloJhdUZqF722KjTW0oP7nJ5cz5r43dPkR54XjO2ieq+HsOXk2JGClMrLh -XG3yrceo03TQOdi2mp3xiX1vOe/BRApn0mQW94afHpzzgBwoK/GOx8KHbFfpDqhe -l3xsnLLOl2UoeHF3FQVM4UiCAGWDZnW6kBZr4mfEWg9liRMV57B3CwQaqcPqCtoY -Ti6tN2jgcBoKfowpiYx6u8NuRg+ZYkG25VEJpVuFOFt9lnlD7PeaEVTRasPsV+4P -TJaILAttW/HcEk5oS1yVHy9Fi7EcNozzEEnSkGUX2lcOawhdnhNrt9t3foW0zWAz -405nt/BNMtMcSscgoR/61/u9DbwJasW6VFoT/oup0bPvk2ok3v/xaSlkal8Wy5OY -K8DF5JGnEIWM/Dwl9ubsI+C+L1JZTHa0eePN8IE3bwoE6IBGgERL81B1kfdgIT+X -yuqEIQRRe1zdDDAgaxMrjYf3JntMli5Bd66G+ykmM1hfK/C+NPM= -=LVUD +iQIzBAABCgAdFiEEynwsejDI6OEnSoR2UcZzBf/C5cAFAmYeJvgACgkQUcZzBf/C +5cCOqQ/8D0idBljs/5h0aJmBcLdThaoESPRpGUzIVfU860mPAViajDSj9goyGdlD +OclH2iBakSQv3QoImnWszHfLW7i3Xxmy+/FomyQrrrOZ4tJnEgC/v94AXpFX33lu +5Ew4sXDrFV2TpT7kgi354aq8m8LXZFSWio4tiOrxqBdGCUVWbap+5dbU7HuxrM4k +R3suUGSgde9VNnFZaK6qC0Tudtjkrhb/NKuH/rMRxwMtPbdT61r7p37PoP51JJ25 +vjtnGklbzG+Y7NdOkv6c+8RwfSra5oQx2MUxrWRKEVluUKDD7ZhSW3wIZgV7GJih +REyqHFBiW3V19csTWGyVdKmPXxCbRGhAiawb/qFGoOgxQBXKbF3zZ/5WDQUdwPYX +6XoFRzJ2Ey4TjiXHXvMlQhIQxSsUEWoeZcLqas5y9wLN6xBVR3PLA/qgpkMK165K +SGKRUW78FKtNjpYWalbrjBe8HWlQ9Ho9G1hOACjYUxeBTaO8UwfJ7JKjhEOXuzcH +1/mJ0hKg94lyV0ONp6xqtV3E/sIJOW/h7X9HcftA/J0CSZOWVwAIJJ3tvAoe+4Z3 +pzCs6JWJR8lec+9p+oFaiSSNsPa1xwSDeaWZ2Wf8RTc0gUr0umQz9gyiQr6i6AMR +7qpCMCss4hTN/4ZAkvunKNEvgcWX9PUy5m4snNCN/D+y44hVRZo= +=FwC0 -----END PGP SIGNATURE----- diff --git a/site/vendor/shipmonk/composer-dependency-analyser/README.md b/site/vendor/shipmonk/composer-dependency-analyser/README.md index 6b8a98b1a..ebc6182a8 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/README.md +++ b/site/vendor/shipmonk/composer-dependency-analyser/README.md @@ -72,6 +72,8 @@ This tool reads your `composer.json` and scans all paths listed in `autoload` & ### Unknown classes - Any class that cannot be autoloaded gets reported as we cannot say if that one is shadowed or not +### Unknown functions + - Any function that is used, but not defined during runtime gets reported as we cannot say if that one is shadowed or not ## Cli options: - `--composer-json path/to/composer.json` for custom path to composer.json @@ -80,7 +82,9 @@ This tool reads your `composer.json` and scans all paths listed in `autoload` & - `--help` display usage & cli options - `--verbose` to see more example classes & usages - `--show-all-usages` to see all usages +- `--format` to use different output format, available are: console (default), junit - `--ignore-unknown-classes` to globally ignore unknown classes +- `--ignore-unknown-functions` to globally ignore unknown functions - `--ignore-shadow-deps` to globally ignore shadow dependencies - `--ignore-unused-deps` to globally ignore unused dependencies - `--ignore-dev-in-prod-deps` to globally ignore dev dependencies in prod code @@ -102,68 +106,39 @@ use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType; $config = new Configuration(); return $config - // disable scanning autoload & autoload-dev paths from composer.json - // with such option, you should add custom paths by addPathToScan() or addPathsToScan() - ->disableComposerAutoloadPathScan() - - // report unused dependencies even for dev packages - // dev packages are often used only in CI, so this is not enabled by default - // but you may want to ignore those packages manually to be sure - ->enableAnalysisOfUnusedDevDependencies() - - // do not report ignores that never matched any error - ->disableReportingUnmatchedIgnores() - - // globally disable specific error type - ->ignoreErrors([ErrorType::DEV_DEPENDENCY_IN_PROD]) - - // overwrite file extensions to scan, defaults to 'php' - // applies only to directory scanning, not directly listed files - ->setFileExtensions(['php']) - - // add extra path to scan - // for multiple paths at once, use addPathsToScan() + //// Adjusting scanned paths ->addPathToScan(__DIR__ . '/build', isDev: false) - - // exclude path from scanning - // for multiple paths at once, use addPathsToExclude() ->addPathToExclude(__DIR__ . '/samples') + ->disableComposerAutoloadPathScan() // disable automatic scan of autoload & autoload-dev paths from composer.json + ->setFileExtensions(['php']) // applies only to directory scanning, not directly listed files - // ignore errors on specific paths - // this can be handy when DIC container file was passed as extra path, but you want to ignore shadow dependencies there - // for multiple paths at once, use ignoreErrorsOnPaths() + //// Ignoring errors + ->ignoreErrors([ErrorType::DEV_DEPENDENCY_IN_PROD]) ->ignoreErrorsOnPath(__DIR__ . '/cache/DIC.php', [ErrorType::SHADOW_DEPENDENCY]) - - // ignore errors on specific packages - // you might have various reasons to ignore certain errors - // e.g. polyfills are often used in libraries, but those are obviously unused when running with latest PHP - // for multiple packages at once, use ignoreErrorsOnPackages() ->ignoreErrorsOnPackage('symfony/polyfill-php73', [ErrorType::UNUSED_DEPENDENCY]) - - // ignore errors on specific packages and paths - // for multiple, use ignoreErrorsOnPackagesAndPaths() or ignoreErrorsOnPackageAndPaths() ->ignoreErrorsOnPackageAndPath('symfony/console', __DIR__ . '/src/OptionalCommand.php', [ErrorType::SHADOW_DEPENDENCY]) - // allow using classes not present in composer's autoloader - // e.g. a library may conditionally support some feature only when Memcached is available + //// Ignoring unknown symbols ->ignoreUnknownClasses(['Memcached']) + ->ignoreUnknownClassesRegex('~^DDTrace~') + ->ignoreUnknownFunctions(['opcache_invalidate']) + ->ignoreUnknownFunctionsRegex('~^opcache_~') - // allow using classes not present in composer's autoloader by regex - // e.g. when you want to ignore whole namespace of classes - ->ignoreUnknownClassesRegex('~^PHPStan\\.*?~') + //// Adjust analysis + ->enableAnalysisOfUnusedDevDependencies() // dev packages are often used only in CI, so this is not enabled by default + ->disableReportingUnmatchedIgnores() // do not report ignores that never matched any error - // force certain classes to be treated as used - // handy when dealing with dependencies in non-php files (e.g. DIC config), see example below - // beware that those are not validated and do not even trigger unknown class error + //// Use symbols from yaml/xml/neon files + // - designed for DIC config files (see below) + // - beware that those are not validated and do not even trigger unknown class error ->addForceUsedSymbols($classesExtractedFromNeonJsonYamlXmlEtc) -; ``` All paths are expected to exist. If you need some glob functionality, you can do it in your config file and pass the expanded list to e.g. `ignoreErrorsOnPaths`. ### Detecting classes from non-php files: -Simplest fuzzy search for classnames within your yaml/neon/xml/json files might look like this: +Some classes might be used only in your DIC config files. Here is a simple way to extract those: ```php $classNameRegex = '[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*'; // https://www.php.net/manual/en/language.oop5.basic.php @@ -178,19 +153,16 @@ preg_match_all( $config->addForceUsedSymbols($matches[1]); // possibly filter by class_exists || interface_exists ``` -Similar approach should help you to avoid false positives in unused dependencies due to the usages being present in e.g. DIC config files only. +Similar approach should help you to avoid false positives in unused dependencies. Another approach for DIC-only usages is to scan the generated php file, but that gave us worse results. +### Scanning codebase located elsewhere: +- This can be done by pointing `--composer-json` to `composer.json` of the other codebase + ## Limitations: - Extension dependencies are not analysed (e.g. `ext-json`) - Files without namespace has limited support - - Only classes with use statements and FQNs are detected -- Function and constant usages are not analysed - - Therefore, if some package contains only functions, it will be reported as unused - ------ - -Despite those limitations, our experience is that this composer-dependency-analyser works much better than composer-unused and composer-require-checker. + - Only symbols with use statements and FQNs are detected ## Contributing: - Check your code by `composer check` @@ -200,4 +172,3 @@ Despite those limitations, our experience is that this composer-dependency-analy ## Supported PHP versions - Runtime requires PHP 7.2 - 8.3 - Scanned codebase should use PHP >= 5.3 - - This can be done by pointing `--composer-json` to codebase located elsewhere diff --git a/site/vendor/shipmonk/composer-dependency-analyser/bin/composer-dependency-analyser b/site/vendor/shipmonk/composer-dependency-analyser/bin/composer-dependency-analyser index 0f8b04cfc..e86fdde99 100755 --- a/site/vendor/shipmonk/composer-dependency-analyser/bin/composer-dependency-analyser +++ b/site/vendor/shipmonk/composer-dependency-analyser/bin/composer-dependency-analyser @@ -7,7 +7,6 @@ use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidConfigException; use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidPathException; use ShipMonk\ComposerDependencyAnalyser\Initializer; use ShipMonk\ComposerDependencyAnalyser\Printer; -use ShipMonk\ComposerDependencyAnalyser\Result\ResultFormatter; use ShipMonk\ComposerDependencyAnalyser\Stopwatch; error_reporting(E_ALL); @@ -42,7 +41,7 @@ try { $analyser = new Analyser($stopwatch, $classLoaders, $configuration, $composerJson->dependencies); $result = $analyser->run(); - $formatter = new ResultFormatter($cwd, $printer); + $formatter = $initializer->initFormatter($options); $exitCode = $formatter->format($result, $options, $configuration); } catch ( diff --git a/site/vendor/shipmonk/composer-dependency-analyser/composer.json b/site/vendor/shipmonk/composer-dependency-analyser/composer.json index 00c0aea1b..c2af7b2e6 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/composer.json +++ b/site/vendor/shipmonk/composer-dependency-analyser/composer.json @@ -24,8 +24,11 @@ "ext-tokenizer": "*" }, "require-dev": { + "ext-dom": "*", + "ext-libxml": "*", "editorconfig-checker/editorconfig-checker": "^10.3.0", "ergebnis/composer-normalize": "^2.19", + "phpcompatibility/php-compatibility": "^9.3", "phpstan/phpstan": "^1.10.63", "phpstan/phpstan-phpunit": "^1.1.1", "phpstan/phpstan-strict-rules": "^1.2.3", diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Analyser.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Analyser.php index ac2e5c5b8..72de2fec5 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Analyser.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Analyser.php @@ -10,13 +10,13 @@ use RecursiveIteratorIterator; use ReflectionClass; use ReflectionException; +use ReflectionFunction; use ShipMonk\ComposerDependencyAnalyser\Config\Configuration; use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType; use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidPathException; use ShipMonk\ComposerDependencyAnalyser\Result\AnalysisResult; use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage; use UnexpectedValueException; -use function array_change_key_case; use function array_diff; use function array_filter; use function array_key_exists; @@ -30,13 +30,13 @@ use function get_defined_functions; use function in_array; use function is_file; +use function is_string; use function str_replace; use function strlen; use function strpos; use function strtolower; use function substr; use function trim; -use const CASE_LOWER; use const DIRECTORY_SEPARATOR; class Analyser @@ -80,6 +80,13 @@ class Analyser */ private $ignoredSymbols; + /** + * function name => path + * + * @var array + */ + private $definedFunctions = []; + /** * @param array $classLoaders vendorDir => ClassLoader (e.g. result of \Composer\Autoload\ClassLoader::getRegisteredLoaders()) * @param array $composerJsonDependencies package name => is dev dependency @@ -94,7 +101,8 @@ public function __construct( $this->stopwatch = $stopwatch; $this->config = $config; $this->composerJsonDependencies = $composerJsonDependencies; - $this->ignoredSymbols = $this->getIgnoredSymbols(); + + $this->initExistingSymbols(); foreach ($classLoaders as $vendorDir => $classLoader) { $this->classLoaders[$vendorDir] = $classLoader; @@ -109,7 +117,8 @@ public function run(): AnalysisResult $this->stopwatch->start(); $scannedFilesCount = 0; - $classmapErrors = []; + $unknownClassErrors = []; + $unknownFunctionErrors = []; $shadowErrors = []; $devInProdErrors = []; $prodOnlyInDevErrors = []; @@ -125,59 +134,69 @@ public function run(): AnalysisResult foreach ($this->getUniqueFilePathsToScan() as $filePath => $isDevFilePath) { $scannedFilesCount++; - foreach ($this->getUsedSymbolsInFile($filePath) as $usedSymbol => $lineNumbers) { - if (isset($this->ignoredSymbols[strtolower($usedSymbol)])) { - continue; - } + $usedSymbolsByKind = $this->getUsedSymbolsInFile($filePath); - $symbolPath = $this->getSymbolPath($usedSymbol); + foreach ($usedSymbolsByKind as $kind => $usedSymbols) { + foreach ($usedSymbols as $usedSymbol => $lineNumbers) { + if (isset($this->ignoredSymbols[$usedSymbol])) { + continue; + } - if ($symbolPath === null) { - if (!$ignoreList->shouldIgnoreUnknownClass($usedSymbol, $filePath)) { - foreach ($lineNumbers as $lineNumber) { - $classmapErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); + $symbolPath = $this->getSymbolPath($usedSymbol, $kind); + + if ($symbolPath === null) { + if ($kind === SymbolKind::CLASSLIKE && !$ignoreList->shouldIgnoreUnknownClass($usedSymbol, $filePath)) { + foreach ($lineNumbers as $lineNumber) { + $unknownClassErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind); + } } - } - continue; - } + if ($kind === SymbolKind::FUNCTION && !$ignoreList->shouldIgnoreUnknownFunction($usedSymbol, $filePath)) { + foreach ($lineNumbers as $lineNumber) { + $unknownFunctionErrors[$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind); + } + } - if (!$this->isVendorPath($symbolPath)) { - continue; // local class - } + continue; + } - $packageName = $this->getPackageNameFromVendorPath($symbolPath); + if (!$this->isVendorPath($symbolPath)) { + continue; // local class + } - if ( - $this->isShadowDependency($packageName) - && !$ignoreList->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $packageName) - ) { - foreach ($lineNumbers as $lineNumber) { - $shadowErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); + $packageName = $this->getPackageNameFromVendorPath($symbolPath); + + if ( + $this->isShadowDependency($packageName) + && !$ignoreList->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, $filePath, $packageName) + ) { + foreach ($lineNumbers as $lineNumber) { + $shadowErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind); + } } - } - if ( - !$isDevFilePath - && $this->isDevDependency($packageName) - && !$ignoreList->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName) - ) { - foreach ($lineNumbers as $lineNumber) { - $devInProdErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); + if ( + !$isDevFilePath + && $this->isDevDependency($packageName) + && !$ignoreList->shouldIgnoreError(ErrorType::DEV_DEPENDENCY_IN_PROD, $filePath, $packageName) + ) { + foreach ($lineNumbers as $lineNumber) { + $devInProdErrors[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind); + } } - } - if ( - !$isDevFilePath - && !$this->isDevDependency($packageName) - ) { - $prodPackagesUsedInProdPath[$packageName] = true; - } + if ( + !$isDevFilePath + && !$this->isDevDependency($packageName) + ) { + $prodPackagesUsedInProdPath[$packageName] = true; + } - $usedPackages[$packageName] = true; + $usedPackages[$packageName] = true; - foreach ($lineNumbers as $lineNumber) { - $usages[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber); + foreach ($lineNumbers as $lineNumber) { + $usages[$packageName][$usedSymbol][] = new SymbolUsage($filePath, $lineNumber, $kind); + } } } } @@ -189,7 +208,7 @@ public function run(): AnalysisResult continue; } - $symbolPath = $this->getSymbolPath($forceUsedSymbol); + $symbolPath = $this->getSymbolPath($forceUsedSymbol, null); if ($symbolPath === null || !$this->isVendorPath($symbolPath)) { continue; @@ -239,7 +258,8 @@ public function run(): AnalysisResult $scannedFilesCount, $this->stopwatch->stop(), $usages, - $classmapErrors, + $unknownClassErrors, + $unknownFunctionErrors, $shadowErrors, $devInProdErrors, $prodOnlyInDevErrors, @@ -297,7 +317,7 @@ private function getPackageNameFromVendorPath(string $realPath): string } /** - * @return array> + * @return array>> * @throws InvalidPathException */ private function getUsedSymbolsInFile(string $filePath): array @@ -308,7 +328,7 @@ private function getUsedSymbolsInFile(string $filePath): array throw new InvalidPathException("Unable to get contents of '$filePath'"); } - return (new UsedSymbolExtractor($code))->parseUsedClasses(); + return (new UsedSymbolExtractor($code))->parseUsedSymbols(); } /** @@ -325,7 +345,7 @@ private function listPhpFilesIn(string $path): Generator try { $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); } catch (UnexpectedValueException $e) { - throw new InvalidPathException("Unable to list files in $path", 0, $e); + throw new InvalidPathException("Unable to list files in $path", $e); } foreach ($iterator as $entry) { @@ -349,8 +369,20 @@ private function isVendorPath(string $realPath): bool return false; } - private function getSymbolPath(string $symbol): ?string + private function getSymbolPath(string $symbol, ?int $kind): ?string { + if ($kind === SymbolKind::FUNCTION || $kind === null) { + $lowerSymbol = strtolower($symbol); + + if (isset($this->definedFunctions[$lowerSymbol])) { + return $this->definedFunctions[$lowerSymbol]; + } + + if ($kind === SymbolKind::FUNCTION) { + return null; + } + } + if (!array_key_exists($symbol, $this->classmap)) { $path = $this->detectFileByClassLoader($symbol) ?? $this->detectFileByReflection($symbol); $this->classmap[$symbol] = $path === null @@ -406,12 +438,9 @@ private function normalizePath(string $filePath): string return Path::normalize($filePath); } - /** - * @return array - */ - private function getIgnoredSymbols(): array + private function initExistingSymbols(): void { - $ignoredSymbols = [ + $this->ignoredSymbols = [ // built-in types 'bool' => true, 'int' => true, @@ -446,12 +475,19 @@ private function getIgnoredSymbols(): array /** @var string $constantName */ foreach (get_defined_constants() as $constantName => $constantValue) { - $ignoredSymbols[$constantName] = true; + $this->ignoredSymbols[$constantName] = true; } foreach (get_defined_functions() as $functionNames) { foreach ($functionNames as $functionName) { - $ignoredSymbols[$functionName] = true; + $reflectionFunction = new ReflectionFunction($functionName); + $functionFilePath = $reflectionFunction->getFileName(); + + if ($reflectionFunction->getExtension() === null && is_string($functionFilePath)) { + $this->definedFunctions[$functionName] = Path::normalize($functionFilePath); + } else { + $this->ignoredSymbols[$functionName] = true; + } } } @@ -464,12 +500,10 @@ private function getIgnoredSymbols(): array foreach ($classLikes as $classLikeNames) { foreach ($classLikeNames as $classLikeName) { if ((new ReflectionClass($classLikeName))->getExtension() !== null) { - $ignoredSymbols[$classLikeName] = true; + $this->ignoredSymbols[$classLikeName] = true; } } } - - return array_change_key_case($ignoredSymbols, CASE_LOWER); // get_defined_functions returns lowercase functions } } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Cli.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Cli.php index 685915b4f..a10115b3b 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Cli.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Cli.php @@ -20,10 +20,13 @@ class Cli 'ignore-dev-in-prod-deps' => false, 'ignore-prod-only-in-dev-deps' => false, 'ignore-unknown-classes' => false, + 'ignore-unknown-functions' => false, + 'ignore-unknown-symbols' => false, 'composer-json' => true, 'config' => true, 'dump-usages' => true, 'show-all-usages' => false, + 'format' => true, ]; /** @@ -151,6 +154,10 @@ public function getProvidedOptions(): CliOptions $options->ignoreUnknownClasses = true; } + if (isset($this->providedOptions['ignore-unknown-functions'])) { + $options->ignoreUnknownFunctions = true; + } + if (isset($this->providedOptions['composer-json'])) { $options->composerJson = $this->providedOptions['composer-json']; // @phpstan-ignore-line type is ensured } @@ -167,6 +174,10 @@ public function getProvidedOptions(): CliOptions $options->showAllUsages = true; } + if (isset($this->providedOptions['format'])) { + $options->format = $this->providedOptions['format']; // @phpstan-ignore-line type is ensured + } + return $options; } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/CliOptions.php b/site/vendor/shipmonk/composer-dependency-analyser/src/CliOptions.php index eb8a8ef73..a0c06b7b2 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/CliOptions.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/CliOptions.php @@ -40,6 +40,11 @@ class CliOptions */ public $ignoreUnknownClasses = null; + /** + * @var true|null + */ + public $ignoreUnknownFunctions = null; + /** * @var string|null */ @@ -60,4 +65,9 @@ class CliOptions */ public $showAllUsages = null; + /** + * @var string|null + */ + public $format = null; + } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/ComposerJson.php b/site/vendor/shipmonk/composer-dependency-analyser/src/ComposerJson.php index b01d844d4..b9feedc5a 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/ComposerJson.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/ComposerJson.php @@ -112,13 +112,13 @@ private function extractAutoloadPaths(string $basePath, array $autoload, bool $i } foreach ($globPaths as $globPath) { - $result[Path::realpath($globPath)] = $isDev; + $result[Path::normalize($globPath)] = $isDev; } continue; } - $result[Path::realpath($absolutePath)] = $isDev; + $result[Path::normalize($absolutePath)] = $isDev; } } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Configuration.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Configuration.php index 9eeaf29fe..0411dcf91 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Configuration.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Configuration.php @@ -79,6 +79,16 @@ class Configuration */ private $ignoredUnknownClassesRegexes = []; + /** + * @var list + */ + private $ignoredUnknownFunctions = []; + + /** + * @var list + */ + private $ignoredUnknownFunctionsRegexes = []; + /** * @return $this */ @@ -319,6 +329,17 @@ public function ignoreUnknownClasses(array $classNames): self return $this; } + /** + * @param list $functionNames + * @return $this + */ + public function ignoreUnknownFunctions(array $functionNames): self + { + $this->ignoredUnknownFunctions = array_merge($this->ignoredUnknownFunctions, $functionNames); + + return $this; + } + /** * @return $this * @throws InvalidConfigException @@ -333,6 +354,20 @@ public function ignoreUnknownClassesRegex(string $classNameRegex): self return $this; } + /** + * @return $this + * @throws InvalidConfigException + */ + public function ignoreUnknownFunctionsRegex(string $functionNameRegex): self + { + if (@preg_match($functionNameRegex, '') === false) { + throw new InvalidConfigException("Invalid regex '$functionNameRegex'"); + } + + $this->ignoredUnknownFunctionsRegexes[] = $functionNameRegex; + return $this; + } + public function getIgnoreList(): IgnoreList { return new IgnoreList( @@ -341,7 +376,9 @@ public function getIgnoreList(): IgnoreList $this->ignoredErrorsOnPackage, $this->ignoredErrorsOnPackageAndPath, $this->ignoredUnknownClasses, - $this->ignoredUnknownClassesRegexes + $this->ignoredUnknownClassesRegexes, + $this->ignoredUnknownFunctions, + $this->ignoredUnknownFunctionsRegexes ); } @@ -436,6 +473,10 @@ private function checkAllowedErrorTypeForPackageIgnore(array $errorTypes): void if (in_array(ErrorType::UNKNOWN_CLASS, $errorTypes, true)) { throw new InvalidConfigException('UNKNOWN_CLASS errors cannot be ignored on a package'); } + + if (in_array(ErrorType::UNKNOWN_FUNCTION, $errorTypes, true)) { + throw new InvalidConfigException('UNKNOWN_FUNCTION errors cannot be ignored on a package'); + } } } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/ErrorType.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/ErrorType.php index dc133fb46..4fa92235c 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/ErrorType.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/ErrorType.php @@ -6,6 +6,7 @@ final class ErrorType { public const UNKNOWN_CLASS = 'unknown-class'; + public const UNKNOWN_FUNCTION = 'unknown-function'; public const SHADOW_DEPENDENCY = 'shadow-dependency'; public const UNUSED_DEPENDENCY = 'unused-dependency'; public const DEV_DEPENDENCY_IN_PROD = 'dev-dependency-in-prod'; diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/IgnoreList.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/IgnoreList.php index 9bbc5ad70..59da5a544 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/IgnoreList.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/IgnoreList.php @@ -4,6 +4,7 @@ use LogicException; use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType; +use ShipMonk\ComposerDependencyAnalyser\SymbolKind; use function array_fill_keys; use function preg_match; use function strpos; @@ -41,6 +42,16 @@ class IgnoreList */ private $ignoredUnknownClassesRegexes; + /** + * @var array + */ + private $ignoredUnknownFunctions; + + /** + * @var array + */ + private $ignoredUnknownFunctionsRegexes; + /** * @param list $ignoredErrors * @param array> $ignoredErrorsOnPath @@ -48,6 +59,8 @@ class IgnoreList * @param array>> $ignoredErrorsOnPackageAndPath * @param list $ignoredUnknownClasses * @param list $ignoredUnknownClassesRegexes + * @param list $ignoredUnknownFunctions + * @param list $ignoredUnknownFunctionsRegexes */ public function __construct( array $ignoredErrors, @@ -55,7 +68,9 @@ public function __construct( array $ignoredErrorsOnPackage, array $ignoredErrorsOnPackageAndPath, array $ignoredUnknownClasses, - array $ignoredUnknownClassesRegexes + array $ignoredUnknownClassesRegexes, + array $ignoredUnknownFunctions, + array $ignoredUnknownFunctionsRegexes ) { $this->ignoredErrors = array_fill_keys($ignoredErrors, false); @@ -76,10 +91,12 @@ public function __construct( $this->ignoredUnknownClasses = array_fill_keys($ignoredUnknownClasses, false); $this->ignoredUnknownClassesRegexes = array_fill_keys($ignoredUnknownClassesRegexes, false); + $this->ignoredUnknownFunctions = array_fill_keys($ignoredUnknownFunctions, false); + $this->ignoredUnknownFunctionsRegexes = array_fill_keys($ignoredUnknownFunctionsRegexes, false); } /** - * @return list + * @return list */ public function getUnusedIgnores(): array { @@ -119,13 +136,25 @@ public function getUnusedIgnores(): array foreach ($this->ignoredUnknownClasses as $class => $ignored) { if (!$ignored) { - $unused[] = new UnusedClassIgnore($class, false); + $unused[] = new UnusedSymbolIgnore($class, false, SymbolKind::CLASSLIKE); } } foreach ($this->ignoredUnknownClassesRegexes as $regex => $ignored) { if (!$ignored) { - $unused[] = new UnusedClassIgnore($regex, true); + $unused[] = new UnusedSymbolIgnore($regex, true, SymbolKind::CLASSLIKE); + } + } + + foreach ($this->ignoredUnknownFunctions as $function => $ignored) { + if (!$ignored) { + $unused[] = new UnusedSymbolIgnore($function, false, SymbolKind::FUNCTION); + } + } + + foreach ($this->ignoredUnknownFunctionsRegexes as $regex => $ignored) { + if (!$ignored) { + $unused[] = new UnusedSymbolIgnore($regex, true, SymbolKind::FUNCTION); } } @@ -142,6 +171,16 @@ public function shouldIgnoreUnknownClass(string $class, string $filePath): bool return $ignoredGlobally || $ignoredByPath || $ignoredByRegex || $ignoredByBlacklist; } + public function shouldIgnoreUnknownFunction(string $function, string $filePath): bool + { + $ignoredGlobally = $this->shouldIgnoreErrorGlobally(ErrorType::UNKNOWN_FUNCTION); + $ignoredByPath = $this->shouldIgnoreErrorOnPath(ErrorType::UNKNOWN_FUNCTION, $filePath); + $ignoredByRegex = $this->shouldIgnoreUnknownFunctionByRegex($function); + $ignoredByBlacklist = $this->shouldIgnoreUnknownFunctionByBlacklist($function); + + return $ignoredGlobally || $ignoredByPath || $ignoredByRegex || $ignoredByBlacklist; + } + private function shouldIgnoreUnknownClassByBlacklist(string $class): bool { if (isset($this->ignoredUnknownClasses[$class])) { @@ -152,6 +191,16 @@ private function shouldIgnoreUnknownClassByBlacklist(string $class): bool return false; } + private function shouldIgnoreUnknownFunctionByBlacklist(string $function): bool + { + if (isset($this->ignoredUnknownFunctions[$function])) { + $this->ignoredUnknownFunctions[$function] = true; + return true; + } + + return false; + } + private function shouldIgnoreUnknownClassByRegex(string $class): bool { foreach ($this->ignoredUnknownClassesRegexes as $regex => $ignoreUsed) { @@ -170,6 +219,24 @@ private function shouldIgnoreUnknownClassByRegex(string $class): bool return false; } + private function shouldIgnoreUnknownFunctionByRegex(string $function): bool + { + foreach ($this->ignoredUnknownFunctionsRegexes as $regex => $ignoreUsed) { + $matches = preg_match($regex, $function); + + if ($matches === false) { + throw new LogicException("Invalid regex: '$regex'"); + } + + if ($matches === 1) { + $this->ignoredUnknownFunctionsRegexes[$regex] = true; + return true; + } + } + + return false; + } + /** * @param ErrorType::SHADOW_DEPENDENCY|ErrorType::UNUSED_DEPENDENCY|ErrorType::DEV_DEPENDENCY_IN_PROD|ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV $errorType */ diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/UnusedClassIgnore.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/UnusedClassIgnore.php deleted file mode 100644 index 89e755b33..000000000 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/UnusedClassIgnore.php +++ /dev/null @@ -1,34 +0,0 @@ -unknownClass = $unknownClass; - $this->isRegex = $isRegex; - } - - public function getUnknownClass(): string - { - return $this->unknownClass; - } - - public function isRegex(): bool - { - return $this->isRegex; - } - -} diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/UnusedSymbolIgnore.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/UnusedSymbolIgnore.php new file mode 100644 index 000000000..e31c69f4e --- /dev/null +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Config/Ignore/UnusedSymbolIgnore.php @@ -0,0 +1,53 @@ +unknownSymbol = $unknownSymbol; + $this->isRegex = $isRegex; + $this->symbolKind = $symbolKind; + } + + public function getUnknownSymbol(): string + { + return $this->unknownSymbol; + } + + public function isRegex(): bool + { + return $this->isRegex; + } + + /** + * @return SymbolKind::CLASSLIKE|SymbolKind::FUNCTION + */ + public function getSymbolKind(): int + { + return $this->symbolKind; + } + +} diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Exception/RuntimeException.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Exception/RuntimeException.php index 2915b11ce..55594b30f 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Exception/RuntimeException.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Exception/RuntimeException.php @@ -3,8 +3,14 @@ namespace ShipMonk\ComposerDependencyAnalyser\Exception; use RuntimeException as NativeRuntimeException; +use Throwable; class RuntimeException extends NativeRuntimeException { + public function __construct(string $message, ?Throwable $previous = null) + { + parent::__construct($message, 0, $previous); + } + } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Initializer.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Initializer.php index f42a336d0..756d47a8e 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Initializer.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Initializer.php @@ -8,6 +8,9 @@ use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidCliException; use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidConfigException; use ShipMonk\ComposerDependencyAnalyser\Exception\InvalidPathException; +use ShipMonk\ComposerDependencyAnalyser\Result\ConsoleFormatter; +use ShipMonk\ComposerDependencyAnalyser\Result\JunitFormatter; +use ShipMonk\ComposerDependencyAnalyser\Result\ResultFormatter; use Throwable; use function count; use function get_class; @@ -32,6 +35,7 @@ class Initializer --composer-json Provide custom path to composer.json --config Provide path to php configuration file (must return \ShipMonk\ComposerDependencyAnalyser\Config\Configuration instance) + --format Change output format. Available values: console (default), junit Ignore options: (or use --config for better granularity) @@ -64,7 +68,6 @@ public function __construct( /** * @throws InvalidConfigException - * @throws InvalidPathException */ public function initConfiguration( CliOptions $options, @@ -89,7 +92,7 @@ public function initConfiguration( return require $configPath; })(); } catch (Throwable $e) { - throw new InvalidConfigException(get_class($e) . " in {$e->getFile()}:{$e->getLine()}\n > " . $e->getMessage(), 0, $e); + throw new InvalidConfigException("Error while loading configuration from '$configPath':\n\n" . get_class($e) . " in {$e->getFile()}:{$e->getLine()}\n > " . $e->getMessage(), $e); } if (!$config instanceof Configuration) { @@ -99,16 +102,21 @@ public function initConfiguration( $config = new Configuration(); } - $ignoreUnknown = $options->ignoreUnknownClasses === true; + $ignoreUnknownClasses = $options->ignoreUnknownClasses === true; + $ignoreUnknownFunctions = $options->ignoreUnknownFunctions === true; $ignoreUnused = $options->ignoreUnusedDeps === true; $ignoreShadow = $options->ignoreShadowDeps === true; $ignoreDevInProd = $options->ignoreDevInProdDeps === true; $ignoreProdOnlyInDev = $options->ignoreProdOnlyInDevDeps === true; - if ($ignoreUnknown) { + if ($ignoreUnknownClasses) { $config->ignoreErrors([ErrorType::UNKNOWN_CLASS]); } + if ($ignoreUnknownFunctions) { + $config->ignoreErrors([ErrorType::UNKNOWN_FUNCTION]); + } + if ($ignoreUnused) { $config->ignoreErrors([ErrorType::UNUSED_DEPENDENCY]); } @@ -126,8 +134,12 @@ public function initConfiguration( } if ($config->shouldScanComposerAutoloadPaths()) { - foreach ($composerJson->autoloadPaths as $absolutePath => $isDevPath) { - $config->addPathToScan($absolutePath, $isDevPath); + try { + foreach ($composerJson->autoloadPaths as $absolutePath => $isDevPath) { + $config->addPathToScan($absolutePath, $isDevPath); + } + } catch (InvalidPathException $e) { + throw new InvalidConfigException('Error while processing composer.json autoload path: ' . $e->getMessage(), $e); } if ($config->getPathsToScan() === []) { @@ -210,4 +222,22 @@ public function initCliOptions(string $cwd, array $argv): CliOptions return $cliOptions; } + /** + * @throws InvalidConfigException + */ + public function initFormatter(CliOptions $options): ResultFormatter + { + switch ($options->format) { + case 'junit': + return new JunitFormatter($this->cwd, $this->printer); + + case 'console': + case null: + return new ConsoleFormatter($this->cwd, $this->printer); + + default: + throw new InvalidConfigException("Invalid format option provided, allowed are 'console' or 'junit'."); + } + } + } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Printer.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Printer.php index edd970eb0..69b5e51d4 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Printer.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Printer.php @@ -26,6 +26,11 @@ public function printLine(string $string): void echo $this->colorize($string) . PHP_EOL; } + public function print(string $string): void + { + echo $this->colorize($string); + } + private function colorize(string $string): string { return str_replace(array_keys(self::COLORS), array_values(self::COLORS), $string); diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/AnalysisResult.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/AnalysisResult.php index bf5dc20ff..ab3ba3080 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/AnalysisResult.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/AnalysisResult.php @@ -2,8 +2,8 @@ namespace ShipMonk\ComposerDependencyAnalyser\Result; -use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedClassIgnore; use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore; +use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedSymbolIgnore; use function ksort; use function sort; @@ -28,7 +28,12 @@ class AnalysisResult /** * @var array> */ - private $classmapErrors = []; + private $unknownClassErrors; + + /** + * @var array> + */ + private $unknownFunctionErrors; /** * @var array>> @@ -51,24 +56,26 @@ class AnalysisResult private $unusedDependencyErrors; /** - * @var list + * @var list */ private $unusedIgnores; /** * @param array>> $usages package => [ classname => usage[] ] - * @param array> $classmapErrors package => usages + * @param array> $unknownClassErrors package => usages + * @param array> $unknownFunctionErrors package => usages * @param array>> $shadowDependencyErrors package => [ classname => usage[] ] * @param array>> $devDependencyInProductionErrors package => [ classname => usage[] ] * @param list $prodDependencyOnlyInDevErrors package[] * @param list $unusedDependencyErrors package[] - * @param list $unusedIgnores + * @param list $unusedIgnores */ public function __construct( int $scannedFilesCount, float $elapsedTime, array $usages, - array $classmapErrors, + array $unknownClassErrors, + array $unknownFunctionErrors, array $shadowDependencyErrors, array $devDependencyInProductionErrors, array $prodDependencyOnlyInDevErrors, @@ -77,7 +84,8 @@ public function __construct( ) { ksort($usages); - ksort($classmapErrors); + ksort($unknownClassErrors); + ksort($unknownFunctionErrors); ksort($shadowDependencyErrors); ksort($devDependencyInProductionErrors); sort($prodDependencyOnlyInDevErrors); @@ -85,7 +93,8 @@ public function __construct( $this->scannedFilesCount = $scannedFilesCount; $this->elapsedTime = $elapsedTime; - $this->classmapErrors = $classmapErrors; + $this->unknownClassErrors = $unknownClassErrors; + $this->unknownFunctionErrors = $unknownFunctionErrors; foreach ($usages as $package => $classes) { ksort($classes); @@ -128,9 +137,17 @@ public function getUsages(): array /** * @return array> */ - public function getClassmapErrors(): array + public function getUnknownClassErrors(): array { - return $this->classmapErrors; + return $this->unknownClassErrors; + } + + /** + * @return array> + */ + public function getUnknownFunctionErrors(): array + { + return $this->unknownFunctionErrors; } /** @@ -166,20 +183,11 @@ public function getUnusedDependencyErrors(): array } /** - * @return list + * @return list */ public function getUnusedIgnores(): array { return $this->unusedIgnores; } - public function hasNoErrors(): bool - { - return $this->unusedDependencyErrors === [] - && $this->classmapErrors === [] - && $this->devDependencyInProductionErrors === [] - && $this->prodDependencyOnlyInDevErrors === [] - && $this->shadowDependencyErrors === []; - } - } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ConsoleFormatter.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ConsoleFormatter.php new file mode 100644 index 000000000..7de0cea3f --- /dev/null +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ConsoleFormatter.php @@ -0,0 +1,436 @@ +cwd = $cwd; + $this->printer = $printer; + } + + public function format( + AnalysisResult $result, + CliOptions $options, + Configuration $configuration + ): int + { + if ($options->dumpUsages !== null) { + return $this->printResultUsages($result, $options->dumpUsages, $options->showAllUsages === true); + } + + return $this->printResultErrors($result, $this->getMaxUsagesShownForErrors($options), $configuration->shouldReportUnmatchedIgnoredErrors()); + } + + private function getMaxUsagesShownForErrors(CliOptions $options): int + { + if ($options->verbose === true) { + return self::VERBOSE_SHOWN_USAGES; + } + + if ($options->showAllUsages === true) { + return PHP_INT_MAX; + } + + return 1; + } + + private function printResultUsages( + AnalysisResult $result, + string $package, + bool $showAllUsages + ): int + { + $usagesToDump = $this->filterUsagesToDump($result->getUsages(), $package); + $maxShownUsages = $showAllUsages ? PHP_INT_MAX : self::VERBOSE_SHOWN_USAGES; + $totalUsages = $this->countAllUsages($usagesToDump); + $symbolsWithUsage = $this->countSymbolUsages($usagesToDump); + + $title = $showAllUsages ? "Dumping all usages of $package" : "Dumping sample usages of $package"; + + $totalPlural = $totalUsages === 1 ? '' : 's'; + $symbolsPlural = $symbolsWithUsage === 1 ? '' : 's'; + $subtitle = "{$totalUsages} usage{$totalPlural} of {$symbolsWithUsage} symbol{$symbolsPlural} in total"; + + $this->printPackageBasedErrors("$title", $subtitle, $usagesToDump, $maxShownUsages); + + if ($this->willLimitUsages($usagesToDump, $maxShownUsages)) { + $this->printLine("Use --show-all-usages to show all of them\n"); + } + + return 1; + } + + /** + * @param array>> $usages + * @return array>> + */ + private function filterUsagesToDump(array $usages, string $filter): array + { + $result = []; + + foreach ($usages as $package => $usagesPerSymbol) { + if (fnmatch($filter, $package)) { + $result[$package] = $usagesPerSymbol; + } + } + + return $result; + } + + private function printResultErrors( + AnalysisResult $result, + int $maxShownUsages, + bool $reportUnmatchedIgnores + ): int + { + $hasError = false; + $unusedIgnores = $result->getUnusedIgnores(); + + $unknownClassErrors = $result->getUnknownClassErrors(); + $unknownFunctionErrors = $result->getUnknownFunctionErrors(); + $shadowDependencyErrors = $result->getShadowDependencyErrors(); + $devDependencyInProductionErrors = $result->getDevDependencyInProductionErrors(); + $prodDependencyOnlyInDevErrors = $result->getProdDependencyOnlyInDevErrors(); + $unusedDependencyErrors = $result->getUnusedDependencyErrors(); + + if (count($unknownClassErrors) > 0) { + $hasError = true; + $this->printSymbolBasedErrors( + 'Unknown classes!', + 'unable to autoload those, so we cannot check them', + $unknownClassErrors, + $maxShownUsages + ); + } + + if (count($unknownFunctionErrors) > 0) { + $hasError = true; + $this->printSymbolBasedErrors( + 'Unknown functions!', + 'those are not declared, so we cannot check them', + $unknownFunctionErrors, + $maxShownUsages + ); + } + + if (count($shadowDependencyErrors) > 0) { + $hasError = true; + $this->printPackageBasedErrors( + 'Found shadow dependencies!', + 'those are used, but not listed as dependency in composer.json', + $shadowDependencyErrors, + $maxShownUsages + ); + } + + if (count($devDependencyInProductionErrors) > 0) { + $hasError = true; + $this->printPackageBasedErrors( + 'Found dev dependencies in production code!', + 'those should probably be moved to "require" section in composer.json', + $devDependencyInProductionErrors, + $maxShownUsages + ); + } + + if (count($prodDependencyOnlyInDevErrors) > 0) { + $hasError = true; + $this->printPackageBasedErrors( + 'Found prod dependencies used only in dev paths!', + 'those should probably be moved to "require-dev" section in composer.json', + array_fill_keys($prodDependencyOnlyInDevErrors, []), + $maxShownUsages + ); + } + + if (count($unusedDependencyErrors) > 0) { + $hasError = true; + $this->printPackageBasedErrors( + 'Found unused dependencies!', + 'those are listed in composer.json, but no usage was found in scanned paths', + array_fill_keys($unusedDependencyErrors, []), + $maxShownUsages + ); + } + + if ($unusedIgnores !== [] && $reportUnmatchedIgnores) { + $hasError = true; + $this->printLine(''); + $this->printLine('Some ignored issues never occurred:'); + $this->printUnusedIgnores($unusedIgnores); + } + + if (!$hasError) { + $this->printLine(''); + $this->printLine('No composer issues found'); + } + + $this->printRunSummary($result); + + return $hasError ? 255 : 0; + } + + /** + * @param array> $errors + */ + private function printSymbolBasedErrors(string $title, string $subtitle, array $errors, int $maxShownUsages): void + { + $this->printHeader($title, $subtitle); + + foreach ($errors as $symbol => $usages) { + $this->printLine(" • {$symbol}"); + + if ($maxShownUsages > 1) { + foreach ($usages as $index => $usage) { + $this->printLine(" {$this->relativizeUsage($usage)}"); + + if ($index === $maxShownUsages - 1) { + $restUsagesCount = count($usages) - $index - 1; + + if ($restUsagesCount > 0) { + $this->printLine(" + {$restUsagesCount} more"); + break; + } + } + } + + $this->printLine(''); + + } else { + $firstUsage = $usages[0]; + $restUsagesCount = count($usages) - 1; + $rest = $restUsagesCount > 0 ? " (+ {$restUsagesCount} more)" : ''; + $this->printLine(" in {$this->relativizeUsage($firstUsage)}$rest" . PHP_EOL); + } + } + + $this->printLine(''); + } + + /** + * @param array>> $errors + */ + private function printPackageBasedErrors(string $title, string $subtitle, array $errors, int $maxShownUsages): void + { + $this->printHeader($title, $subtitle); + + foreach ($errors as $packageName => $usagesPerSymbol) { + $this->printLine(" • {$packageName}"); + + $this->printUsages($usagesPerSymbol, $maxShownUsages); + } + + $this->printLine(''); + } + + /** + * @param array> $usagesPerSymbol + */ + private function printUsages(array $usagesPerSymbol, int $maxShownUsages): void + { + if ($maxShownUsages === 1) { + $countOfAllUsages = array_reduce( + $usagesPerSymbol, + static function (int $carry, array $usages): int { + return $carry + count($usages); + }, + 0 + ); + + foreach ($usagesPerSymbol as $symbol => $usages) { + $firstUsage = $usages[0]; + $restUsagesCount = $countOfAllUsages - 1; + $rest = $countOfAllUsages > 1 ? " (+ {$restUsagesCount} more)" : ''; + $this->printLine(" e.g. {$symbol} in {$this->relativizeUsage($firstUsage)}$rest" . PHP_EOL); + break; + } + } else { + $symbolsPrinted = 0; + + foreach ($usagesPerSymbol as $symbol => $usages) { + $symbolsPrinted++; + $this->printLine(" {$symbol}"); + + foreach ($usages as $index => $usage) { + $this->printLine(" {$this->relativizeUsage($usage)}"); + + if ($index === $maxShownUsages - 1) { + $restUsagesCount = count($usages) - $index - 1; + + if ($restUsagesCount > 0) { + $this->printLine(" + {$restUsagesCount} more"); + break; + } + } + } + + if ($symbolsPrinted === $maxShownUsages) { + $restSymbolsCount = count($usagesPerSymbol) - $symbolsPrinted; + + if ($restSymbolsCount > 0) { + $this->printLine(" + {$restSymbolsCount} more symbol" . ($restSymbolsCount > 1 ? 's' : '')); + break; + } + } + } + } + } + + private function printHeader(string $title, string $subtitle): void + { + $this->printLine(''); + $this->printLine("$title"); + $this->printLine("($subtitle)" . PHP_EOL); + } + + private function printLine(string $string): void + { + $this->printer->printLine($string); + } + + private function relativizeUsage(SymbolUsage $usage): string + { + return "{$this->relativizePath($usage->getFilepath())}:{$usage->getLineNumber()}"; + } + + private function relativizePath(string $path): string + { + if (strpos($path, $this->cwd) === 0) { + return (string) substr($path, strlen($this->cwd) + 1); + } + + return $path; + } + + /** + * @param list $unusedIgnores + */ + private function printUnusedIgnores(array $unusedIgnores): void + { + foreach ($unusedIgnores as $unusedIgnore) { + if ($unusedIgnore instanceof UnusedSymbolIgnore) { + $this->printSymbolBasedUnusedIgnore($unusedIgnore); + } else { + $this->printErrorBasedUnusedIgnore($unusedIgnore); + } + } + + $this->printLine(''); + } + + private function printSymbolBasedUnusedIgnore(UnusedSymbolIgnore $unusedIgnore): void + { + $kind = $unusedIgnore->getSymbolKind() === SymbolKind::CLASSLIKE ? 'class' : 'function'; + $regex = $unusedIgnore->isRegex() ? ' regex' : ''; + $this->printLine(" • Unknown {$kind}{$regex} '{$unusedIgnore->getUnknownSymbol()}' was ignored, but it was never applied."); + } + + private function printErrorBasedUnusedIgnore(UnusedErrorIgnore $unusedIgnore): void + { + $package = $unusedIgnore->getPackage(); + $path = $unusedIgnore->getPath(); + + if ($package === null && $path === null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was globally ignored, but it was never applied."); + } + + if ($package !== null && $path === null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for package '{$package}', but it was never applied."); + } + + if ($package === null && $path !== null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for path '{$this->relativizePath($path)}', but it was never applied."); + } + + if ($package !== null && $path !== null) { + $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for package '{$package}' and path '{$this->relativizePath($path)}', but it was never applied."); + } + } + + private function printRunSummary(AnalysisResult $result): void + { + $elapsed = round($result->getElapsedTime(), 3); + $this->printLine("(scanned {$result->getScannedFilesCount()} files in {$elapsed} s)" . PHP_EOL); + } + + /** + * @param array>> $usages + */ + private function countAllUsages(array $usages): int + { + $total = 0; + + foreach ($usages as $usagesPerSymbol) { + foreach ($usagesPerSymbol as $symbolUsages) { + $total += count($symbolUsages); + } + } + + return $total; + } + + /** + * @param array>> $usages + */ + private function countSymbolUsages(array $usages): int + { + $total = 0; + + foreach ($usages as $usagesPerSymbol) { + $total += count($usagesPerSymbol); + } + + return $total; + } + + /** + * @param array>> $usages + */ + private function willLimitUsages(array $usages, int $limit): bool + { + foreach ($usages as $usagesPerSymbol) { + if (count($usagesPerSymbol) > $limit) { + return true; + } + + foreach ($usagesPerSymbol as $symbolUsages) { + if (count($symbolUsages) > $limit) { + return true; + } + } + } + + return false; + } + +} diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/JunitFormatter.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/JunitFormatter.php new file mode 100644 index 000000000..f206518d8 --- /dev/null +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/JunitFormatter.php @@ -0,0 +1,326 @@ +cwd = $cwd; + $this->printer = $printer; + } + + public function format( + AnalysisResult $result, + CliOptions $options, + Configuration $configuration + ): int + { + $xml = ''; + $xml .= ''; + + $hasError = false; + $unusedIgnores = $result->getUnusedIgnores(); + + $unknownClassErrors = $result->getUnknownClassErrors(); + $unknownFunctionErrors = $result->getUnknownFunctionErrors(); + $shadowDependencyErrors = $result->getShadowDependencyErrors(); + $devDependencyInProductionErrors = $result->getDevDependencyInProductionErrors(); + $prodDependencyOnlyInDevErrors = $result->getProdDependencyOnlyInDevErrors(); + $unusedDependencyErrors = $result->getUnusedDependencyErrors(); + + $maxShownUsages = $this->getMaxUsagesShownForErrors($options); + + if (count($unknownClassErrors) > 0) { + $hasError = true; + $xml .= $this->createSymbolBasedTestSuite( + 'unknown classes', + $unknownClassErrors, + $maxShownUsages + ); + } + + if (count($unknownFunctionErrors) > 0) { + $hasError = true; + $xml .= $this->createSymbolBasedTestSuite( + 'unknown functions', + $unknownFunctionErrors, + $maxShownUsages + ); + } + + if (count($shadowDependencyErrors) > 0) { + $hasError = true; + $xml .= $this->createPackageBasedTestSuite( + 'shadow dependencies', + $shadowDependencyErrors, + $maxShownUsages + ); + } + + if (count($devDependencyInProductionErrors) > 0) { + $hasError = true; + $xml .= $this->createPackageBasedTestSuite( + 'dev dependencies in production code', + $devDependencyInProductionErrors, + $maxShownUsages + ); + } + + if (count($prodDependencyOnlyInDevErrors) > 0) { + $hasError = true; + $xml .= $this->createPackageBasedTestSuite( + 'prod dependencies used only in dev paths', + array_fill_keys($prodDependencyOnlyInDevErrors, []), + $maxShownUsages + ); + } + + if (count($unusedDependencyErrors) > 0) { + $hasError = true; + $xml .= $this->createPackageBasedTestSuite( + 'unused dependencies', + array_fill_keys($unusedDependencyErrors, []), + $maxShownUsages + ); + } + + if ($unusedIgnores !== [] && $configuration->shouldReportUnmatchedIgnoredErrors()) { + $hasError = true; + $xml .= $this->createUnusedIgnoresTestSuite($unusedIgnores); + } + + $xml .= ''; + + $this->printer->print($xml); + + if ($hasError) { + return 255; + } + + return 0; + } + + private function getMaxUsagesShownForErrors(CliOptions $options): int + { + if ($options->verbose === true) { + return self::VERBOSE_SHOWN_USAGES; + } + + if ($options->showAllUsages === true) { + return PHP_INT_MAX; + } + + return 1; + } + + /** + * @param array> $errors + */ + private function createSymbolBasedTestSuite(string $title, array $errors, int $maxShownUsages): string + { + $xml = sprintf('', $this->escape($title), count($errors)); + + foreach ($errors as $symbol => $usages) { + $xml .= sprintf('', $this->escape($symbol)); + + if ($maxShownUsages > 1) { + $failureUsage = []; + + foreach ($usages as $index => $usage) { + $failureUsage[] = $this->relativizeUsage($usage); + + if ($index === $maxShownUsages - 1) { + $restUsagesCount = count($usages) - $index - 1; + + if ($restUsagesCount > 0) { + $failureUsage[] = "+ {$restUsagesCount} more"; + break; + } + } + } + + $xml .= sprintf('%s', $this->escape(implode('\n', $failureUsage))); + } else { + $firstUsage = $usages[0]; + $restUsagesCount = count($usages) - 1; + $rest = $restUsagesCount > 0 ? " (+ {$restUsagesCount} more)" : ''; + $xml .= sprintf('in %s%s', $this->escape($this->relativizeUsage($firstUsage)), $rest); + } + + $xml .= ''; + } + + $xml .= ''; + + return $xml; + } + + /** + * @param array>> $errors + */ + private function createPackageBasedTestSuite(string $title, array $errors, int $maxShownUsages): string + { + $xml = sprintf('', $this->escape($title), count($errors)); + + foreach ($errors as $packageName => $usagesPerClassname) { + $xml .= sprintf('', $this->escape($packageName)); + $xml .= sprintf('%s', $this->escape(implode('\n', $this->createUsages($usagesPerClassname, $maxShownUsages)))); + $xml .= ''; + } + + $xml .= ''; + + return $xml; + } + + /** + * @param array> $usagesPerSymbol + * @return list + */ + private function createUsages(array $usagesPerSymbol, int $maxShownUsages): array + { + $usageMessages = []; + + if ($maxShownUsages === 1) { + $countOfAllUsages = array_reduce( + $usagesPerSymbol, + static function (int $carry, array $usages): int { + return $carry + count($usages); + }, + 0 + ); + + foreach ($usagesPerSymbol as $symbol => $usages) { + $firstUsage = $usages[0]; + $restUsagesCount = $countOfAllUsages - 1; + $rest = $countOfAllUsages > 1 ? " (+ {$restUsagesCount} more)" : ''; + $usageMessages[] = "e.g. {$symbol} in {$this->relativizeUsage($firstUsage)}$rest"; + break; + } + } else { + $classnamesPrinted = 0; + + foreach ($usagesPerSymbol as $symbol => $usages) { + $classnamesPrinted++; + + $usageMessages[] = $symbol; + + foreach ($usages as $index => $usage) { + $usageMessages[] = " {$this->relativizeUsage($usage)}"; + + if ($index === $maxShownUsages - 1) { + $restUsagesCount = count($usages) - $index - 1; + + if ($restUsagesCount > 0) { + $usageMessages[] = " + {$restUsagesCount} more"; + break; + } + } + } + + if ($classnamesPrinted === $maxShownUsages) { + $restSymbolsCount = count($usagesPerSymbol) - $classnamesPrinted; + + if ($restSymbolsCount > 0) { + $usageMessages[] = " + {$restSymbolsCount} more symbol" . ($restSymbolsCount > 1 ? 's' : ''); + break; + } + } + } + } + + return $usageMessages; + } + + /** + * @param list $unusedIgnores + */ + private function createUnusedIgnoresTestSuite(array $unusedIgnores): string + { + $xml = sprintf('', count($unusedIgnores)); + + foreach ($unusedIgnores as $unusedIgnore) { + if ($unusedIgnore instanceof UnusedSymbolIgnore) { + $kind = $unusedIgnore->getSymbolKind() === SymbolKind::CLASSLIKE ? 'class' : 'function'; + $regex = $unusedIgnore->isRegex() ? ' regex' : ''; + $message = "Unknown {$kind}{$regex} '{$unusedIgnore->getUnknownSymbol()}' was ignored, but it was never applied."; + $xml .= sprintf('%s', $this->escape($unusedIgnore->getUnknownSymbol()), $this->escape($message)); + } else { + $package = $unusedIgnore->getPackage(); + $path = $unusedIgnore->getPath(); + $message = "'{$unusedIgnore->getErrorType()}'"; + + if ($package === null && $path === null) { + $message = "'{$unusedIgnore->getErrorType()}' was globally ignored, but it was never applied."; + } + + if ($package !== null && $path === null) { + $message = "'{$unusedIgnore->getErrorType()}' was ignored for package '{$package}', but it was never applied."; + } + + if ($package === null && $path !== null) { + $message = "'{$unusedIgnore->getErrorType()}' was ignored for path '{$this->relativizePath($path)}', but it was never applied."; + } + + if ($package !== null && $path !== null) { + $message = "'{$unusedIgnore->getErrorType()}' was ignored for package '{$package}' and path '{$this->relativizePath($path)}', but it was never applied."; + } + + $xml .= sprintf('%s', $this->escape($unusedIgnore->getErrorType()), $this->escape($message)); + } + } + + return $xml . ''; + } + + private function relativizeUsage(SymbolUsage $usage): string + { + return "{$this->relativizePath($usage->getFilepath())}:{$usage->getLineNumber()}"; + } + + private function relativizePath(string $path): string + { + if (strpos($path, $this->cwd) === 0) { + return (string) substr($path, strlen($this->cwd) + 1); + } + + return $path; + } + + private function escape(string $string): string + { + return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8'); + } + +} diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ResultFormatter.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ResultFormatter.php index 9358f66c2..0682c87fb 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ResultFormatter.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/ResultFormatter.php @@ -4,422 +4,16 @@ use ShipMonk\ComposerDependencyAnalyser\CliOptions; use ShipMonk\ComposerDependencyAnalyser\Config\Configuration; -use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedClassIgnore; -use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore; -use ShipMonk\ComposerDependencyAnalyser\Printer; -use function array_fill_keys; -use function array_reduce; -use function count; -use function fnmatch; -use function round; -use function strlen; -use function strpos; -use function substr; -use const PHP_EOL; -use const PHP_INT_MAX; -class ResultFormatter +interface ResultFormatter { public const VERBOSE_SHOWN_USAGES = 3; - /** - * @var string - */ - private $cwd; - - /** - * @var Printer - */ - private $printer; - - public function __construct(string $cwd, Printer $printer) - { - $this->cwd = $cwd; - $this->printer = $printer; - } - public function format( AnalysisResult $result, CliOptions $options, Configuration $configuration - ): int - { - if ($options->dumpUsages !== null) { - return $this->printResultUsages($result, $options->dumpUsages, $options->showAllUsages === true); - } - - return $this->printResultErrors($result, $this->getMaxUsagesShownForErrors($options), $configuration->shouldReportUnmatchedIgnoredErrors()); - } - - private function getMaxUsagesShownForErrors(CliOptions $options): int - { - if ($options->verbose === true) { - return self::VERBOSE_SHOWN_USAGES; - } - - if ($options->showAllUsages === true) { - return PHP_INT_MAX; - } - - return 1; - } - - private function printResultUsages( - AnalysisResult $result, - string $package, - bool $showAllUsages - ): int - { - $usagesToDump = $this->filterUsagesToDump($result->getUsages(), $package); - $maxShownUsages = $showAllUsages ? PHP_INT_MAX : self::VERBOSE_SHOWN_USAGES; - $totalUsages = $this->countAllUsages($usagesToDump); - $classesWithUsage = $this->countClassUsages($usagesToDump); - - $title = $showAllUsages ? "Dumping all usages of $package" : "Dumping sample usages of $package"; - - $totalPlural = $totalUsages === 1 ? '' : 's'; - $classesPlural = $classesWithUsage === 1 ? '' : 'es'; - $subtitle = "{$totalUsages} usage{$totalPlural} of {$classesWithUsage} class{$classesPlural} in total"; - - $this->printPackageBasedErrors("$title", $subtitle, $usagesToDump, $maxShownUsages); - - if ($this->willLimitUsages($usagesToDump, $maxShownUsages)) { - $this->printLine("Use --show-all-usages to show all of them\n"); - } - - return 1; - } - - /** - * @param array>> $usages - * @return array>> - */ - private function filterUsagesToDump(array $usages, string $filter): array - { - $result = []; - - foreach ($usages as $package => $usagesPerClassname) { - if (fnmatch($filter, $package)) { - $result[$package] = $usagesPerClassname; - } - } - - return $result; - } - - private function printResultErrors( - AnalysisResult $result, - int $maxShownUsages, - bool $reportUnmatchedIgnores - ): int - { - $hasError = false; - $unusedIgnores = $result->getUnusedIgnores(); - - $classmapErrors = $result->getClassmapErrors(); - $shadowDependencyErrors = $result->getShadowDependencyErrors(); - $devDependencyInProductionErrors = $result->getDevDependencyInProductionErrors(); - $prodDependencyOnlyInDevErrors = $result->getProdDependencyOnlyInDevErrors(); - $unusedDependencyErrors = $result->getUnusedDependencyErrors(); - - if (count($classmapErrors) > 0) { - $hasError = true; - $this->printClassBasedErrors( - 'Unknown classes!', - 'unable to autoload those, so we cannot check them', - $classmapErrors, - $maxShownUsages - ); - } - - if (count($shadowDependencyErrors) > 0) { - $hasError = true; - $this->printPackageBasedErrors( - 'Found shadow dependencies!', - 'those are used, but not listed as dependency in composer.json', - $shadowDependencyErrors, - $maxShownUsages - ); - } - - if (count($devDependencyInProductionErrors) > 0) { - $hasError = true; - $this->printPackageBasedErrors( - 'Found dev dependencies in production code!', - 'those should probably be moved to "require" section in composer.json', - $devDependencyInProductionErrors, - $maxShownUsages - ); - } - - if (count($prodDependencyOnlyInDevErrors) > 0) { - $hasError = true; - $this->printPackageBasedErrors( - 'Found prod dependencies used only in dev paths!', - 'those should probably be moved to "require-dev" section in composer.json', - array_fill_keys($prodDependencyOnlyInDevErrors, []), - $maxShownUsages - ); - } - - if (count($unusedDependencyErrors) > 0) { - $hasError = true; - $this->printPackageBasedErrors( - 'Found unused dependencies!', - 'those are listed in composer.json, but no usage was found in scanned paths', - array_fill_keys($unusedDependencyErrors, []), - $maxShownUsages - ); - } - - if ($unusedIgnores !== [] && $reportUnmatchedIgnores) { - $hasError = true; - $this->printLine(''); - $this->printLine('Some ignored issues never occurred:'); - $this->printUnusedIgnores($unusedIgnores); - } - - if (!$hasError) { - $this->printLine(''); - $this->printLine('No composer issues found'); - } - - $this->printRunSummary($result); - - return $hasError ? 255 : 0; - } - - /** - * @param array> $errors - */ - private function printClassBasedErrors(string $title, string $subtitle, array $errors, int $maxShownUsages): void - { - $this->printHeader($title, $subtitle); - - foreach ($errors as $classname => $usages) { - $this->printLine(" • {$classname}"); - - if ($maxShownUsages > 1) { - foreach ($usages as $index => $usage) { - $this->printLine(" {$this->relativizeUsage($usage)}"); - - if ($index === $maxShownUsages - 1) { - $restUsagesCount = count($usages) - $index - 1; - - if ($restUsagesCount > 0) { - $this->printLine(" + {$restUsagesCount} more"); - break; - } - } - } - - $this->printLine(''); - - } else { - $firstUsage = $usages[0]; - $restUsagesCount = count($usages) - 1; - $rest = $restUsagesCount > 0 ? " (+ {$restUsagesCount} more)" : ''; - $this->printLine(" in {$this->relativizeUsage($firstUsage)}$rest" . PHP_EOL); - } - } - - $this->printLine(''); - } - - /** - * @param array>> $errors - */ - private function printPackageBasedErrors(string $title, string $subtitle, array $errors, int $maxShownUsages): void - { - $this->printHeader($title, $subtitle); - - foreach ($errors as $packageName => $usagesPerClassname) { - $this->printLine(" • {$packageName}"); - - $this->printUsages($usagesPerClassname, $maxShownUsages); - } - - $this->printLine(''); - } - - /** - * @param array> $usagesPerClassname - */ - private function printUsages(array $usagesPerClassname, int $maxShownUsages): void - { - if ($maxShownUsages === 1) { - $countOfAllUsages = array_reduce( - $usagesPerClassname, - static function (int $carry, array $usages): int { - return $carry + count($usages); - }, - 0 - ); - - foreach ($usagesPerClassname as $classname => $usages) { - $firstUsage = $usages[0]; - $restUsagesCount = $countOfAllUsages - 1; - $rest = $countOfAllUsages > 1 ? " (+ {$restUsagesCount} more)" : ''; - $this->printLine(" e.g. {$classname} in {$this->relativizeUsage($firstUsage)}$rest" . PHP_EOL); - break; - } - } else { - $classnamesPrinted = 0; - - foreach ($usagesPerClassname as $classname => $usages) { - $classnamesPrinted++; - $this->printLine(" {$classname}"); - - foreach ($usages as $index => $usage) { - $this->printLine(" {$this->relativizeUsage($usage)}"); - - if ($index === $maxShownUsages - 1) { - $restUsagesCount = count($usages) - $index - 1; - - if ($restUsagesCount > 0) { - $this->printLine(" + {$restUsagesCount} more"); - break; - } - } - } - - if ($classnamesPrinted === $maxShownUsages) { - $restClassnamesCount = count($usagesPerClassname) - $classnamesPrinted; - - if ($restClassnamesCount > 0) { - $this->printLine(" + {$restClassnamesCount} more class" . ($restClassnamesCount > 1 ? 'es' : '')); - break; - } - } - } - } - } - - private function printHeader(string $title, string $subtitle): void - { - $this->printLine(''); - $this->printLine("$title"); - $this->printLine("($subtitle)" . PHP_EOL); - } - - public function printLine(string $string): void - { - $this->printer->printLine($string); - } - - private function relativizeUsage(SymbolUsage $usage): string - { - return "{$this->relativizePath($usage->getFilepath())}:{$usage->getLineNumber()}"; - } - - private function relativizePath(string $path): string - { - if (strpos($path, $this->cwd) === 0) { - return (string) substr($path, strlen($this->cwd) + 1); - } - - return $path; - } - - /** - * @param list $unusedIgnores - */ - private function printUnusedIgnores(array $unusedIgnores): void - { - foreach ($unusedIgnores as $unusedIgnore) { - if ($unusedIgnore instanceof UnusedClassIgnore) { - $this->printClassBasedUnusedIgnore($unusedIgnore); - } else { - $this->printErrorBasedUnusedIgnore($unusedIgnore); - } - } - - $this->printLine(''); - } - - private function printClassBasedUnusedIgnore(UnusedClassIgnore $unusedIgnore): void - { - $regex = $unusedIgnore->isRegex() ? ' regex' : ''; - $this->printLine(" • Unknown class{$regex} '{$unusedIgnore->getUnknownClass()}' was ignored, but it was never applied."); - } - - private function printErrorBasedUnusedIgnore(UnusedErrorIgnore $unusedIgnore): void - { - $package = $unusedIgnore->getPackage(); - $path = $unusedIgnore->getPath(); - - if ($package === null && $path === null) { - $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was globally ignored, but it was never applied."); - } - - if ($package !== null && $path === null) { - $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for package '{$package}', but it was never applied."); - } - - if ($package === null && $path !== null) { - $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for path '{$this->relativizePath($path)}', but it was never applied."); - } - - if ($package !== null && $path !== null) { - $this->printLine(" • Error '{$unusedIgnore->getErrorType()}' was ignored for package '{$package}' and path '{$this->relativizePath($path)}', but it was never applied."); - } - } - - private function printRunSummary(AnalysisResult $result): void - { - $elapsed = round($result->getElapsedTime(), 3); - $this->printLine("(scanned {$result->getScannedFilesCount()} files in {$elapsed} s)" . PHP_EOL); - } - - /** - * @param array>> $usages - */ - private function countAllUsages(array $usages): int - { - $total = 0; - - foreach ($usages as $usagesPerClassname) { - foreach ($usagesPerClassname as $classUsages) { - $total += count($classUsages); - } - } - - return $total; - } - - /** - * @param array>> $usages - */ - private function countClassUsages(array $usages): int - { - $total = 0; - - foreach ($usages as $usagesPerClassname) { - $total += count($usagesPerClassname); - } - - return $total; - } - - /** - * @param array>> $usages - */ - private function willLimitUsages(array $usages, int $limit): bool - { - foreach ($usages as $usagesPerClassname) { - if (count($usagesPerClassname) > $limit) { - return true; - } - - foreach ($usagesPerClassname as $classUsages) { - if (count($classUsages) > $limit) { - return true; - } - } - } - - return false; - } + ): int; } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/SymbolUsage.php b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/SymbolUsage.php index f67f0b59d..5b26a83e8 100644 --- a/site/vendor/shipmonk/composer-dependency-analyser/src/Result/SymbolUsage.php +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/Result/SymbolUsage.php @@ -2,6 +2,8 @@ namespace ShipMonk\ComposerDependencyAnalyser\Result; +use ShipMonk\ComposerDependencyAnalyser\SymbolKind; + class SymbolUsage { @@ -15,10 +17,19 @@ class SymbolUsage */ private $lineNumber; - public function __construct(string $filepath, int $lineNumber) + /** + * @var SymbolKind::* + */ + private $kind; + + /** + * @param SymbolKind::* $kind + */ + public function __construct(string $filepath, int $lineNumber, int $kind) { $this->filepath = $filepath; $this->lineNumber = $lineNumber; + $this->kind = $kind; } public function getFilepath(): string @@ -31,4 +42,12 @@ public function getLineNumber(): int return $this->lineNumber; } + /** + * @return SymbolKind::* + */ + public function getKind(): int + { + return $this->kind; + } + } diff --git a/site/vendor/shipmonk/composer-dependency-analyser/src/SymbolKind.php b/site/vendor/shipmonk/composer-dependency-analyser/src/SymbolKind.php new file mode 100644 index 000000000..8e9cc9239 --- /dev/null +++ b/site/vendor/shipmonk/composer-dependency-analyser/src/SymbolKind.php @@ -0,0 +1,12 @@ +> + * @return array>> * @license Inspired by https://github.com/doctrine/annotations/blob/2.0.0/lib/Doctrine/Common/Annotations/TokenParser.php */ - public function parseUsedClasses(): array + public function parseUsedSymbols(): array { $usedSymbols = []; $useStatements = []; + $useStatementKinds = []; - $level = 0; + $level = 0; // {, }, {$, ${ + $squareLevel = 0; // [, ], #[ $inClassLevel = null; + $inAttributeSquareLevel = null; $numTokens = $this->numTokens; $tokens = $this->tokens; @@ -86,20 +92,26 @@ public function parseUsedClasses(): array case T_USE: if ($inClassLevel === null) { - foreach ($this->parseUseStatement() as $alias => $class) { - $useStatements[$alias] = $this->normalizeBackslash($class); + foreach ($this->parseUseStatement() as $alias => [$fullSymbolName, $symbolKind]) { + $useStatements[$alias] = $this->normalizeBackslash($fullSymbolName); + $useStatementKinds[$alias] = $symbolKind; } } break; + case PHP_VERSION_ID > 80000 ? T_ATTRIBUTE : -1: + $inAttributeSquareLevel = ++$squareLevel; + break; + case PHP_VERSION_ID >= 80000 ? T_NAMESPACE : -1: $useStatements = []; // reset use statements on namespace change break; case PHP_VERSION_ID >= 80000 ? T_NAME_FULLY_QUALIFIED : -1: $symbolName = $this->normalizeBackslash($token[1]); - $usedSymbols[$symbolName][] = $token[2]; + $kind = $this->getFqnSymbolKind($this->pointer - 2, $this->pointer, $inAttributeSquareLevel !== null); + $usedSymbols[$kind][$symbolName][] = $token[2]; break; case PHP_VERSION_ID >= 80000 ? T_NAME_QUALIFIED : -1: @@ -107,7 +119,8 @@ public function parseUsedClasses(): array if (isset($useStatements[$neededAlias])) { $symbolName = $useStatements[$neededAlias] . substr($token[1], strlen($neededAlias)); - $usedSymbols[$symbolName][] = $token[2]; + $kind = $this->getFqnSymbolKind($this->pointer - 2, $this->pointer, $inAttributeSquareLevel !== null); + $usedSymbols[$kind][$symbolName][] = $token[2]; } break; @@ -117,7 +130,8 @@ public function parseUsedClasses(): array if (isset($useStatements[$name])) { $symbolName = $useStatements[$name]; - $usedSymbols[$symbolName][] = $token[2]; + $kind = $useStatementKinds[$name]; + $usedSymbols[$kind][$symbolName][] = $token[2]; } break; @@ -133,27 +147,32 @@ public function parseUsedClasses(): array break; case PHP_VERSION_ID < 80000 ? T_NS_SEPARATOR : -1: + $pointerBeforeName = $this->pointer - 2; $symbolName = $this->normalizeBackslash($this->parseNameForOldPhp()); if ($symbolName !== '') { // e.g. \array (NS separator followed by not-a-name) - $usedSymbols[$symbolName][] = $token[2]; + $kind = $this->getFqnSymbolKind($pointerBeforeName, $this->pointer - 1, false); + $usedSymbols[$kind][$symbolName][] = $token[2]; } break; case PHP_VERSION_ID < 80000 ? T_STRING : -1: + $pointerBeforeName = $this->pointer - 2; $name = $this->parseNameForOldPhp(); if (isset($useStatements[$name])) { // unqualified name $symbolName = $useStatements[$name]; - $usedSymbols[$symbolName][] = $token[2]; + $kind = $useStatementKinds[$name]; + $usedSymbols[$kind][$symbolName][] = $token[2]; } else { [$neededAlias] = explode('\\', $name, 2); if (isset($useStatements[$neededAlias])) { // qualified name $symbolName = $useStatements[$neededAlias] . substr($name, strlen($neededAlias)); - $usedSymbols[$symbolName][] = $token[2]; + $kind = $this->getFqnSymbolKind($pointerBeforeName, $this->pointer - 1, false); + $usedSymbols[$kind][$symbolName][] = $token[2]; } } @@ -172,10 +191,18 @@ public function parseUsedClasses(): array } $level--; + } elseif ($token === '[') { + $squareLevel++; + } elseif ($token === ']') { + if ($squareLevel === $inAttributeSquareLevel) { + $inAttributeSquareLevel = null; + } + + $squareLevel--; } } - return $usedSymbols; + return $usedSymbols; // @phpstan-ignore-line Not enough precise analysis "Offset 'kind' (1|2|3) does not accept type int<1, max>" } /** @@ -200,15 +227,17 @@ private function parseNameForOldPhp(): string } /** - * @return array + * @return array */ - public function parseUseStatement(): array + private function parseUseStatement(): array { $groupRoot = ''; $class = ''; $alias = ''; $statements = []; + $kind = SymbolKind::CLASSLIKE; $explicitAlias = false; + $kindFrozen = false; while ($this->pointer < $this->numTokens) { $token = $this->tokens[$this->pointer++]; @@ -231,6 +260,14 @@ public function parseUseStatement(): array $alias = $classSplit[count($classSplit) - 1]; break; + case T_FUNCTION: + $kind = SymbolKind::FUNCTION; + break; + + case T_CONST: + $kind = SymbolKind::CONSTANT; + break; + case T_NS_SEPARATOR: $class .= '\\'; $alias = ''; @@ -250,14 +287,21 @@ public function parseUseStatement(): array break 2; } } elseif ($token === ',') { - $statements[$alias] = $groupRoot . $class; + $statements[$alias] = [$groupRoot . $class, $kind]; + + if (!$kindFrozen) { + $kind = SymbolKind::CLASSLIKE; + } + $class = ''; $alias = ''; $explicitAlias = false; } elseif ($token === ';') { - $statements[$alias] = $groupRoot . $class; + $statements[$alias] = [$groupRoot . $class, $kind]; + break; } elseif ($token === '{') { + $kindFrozen = ($kind === SymbolKind::FUNCTION || $kind === SymbolKind::CONSTANT); $groupRoot = $class; $class = ''; } elseif ($token === '}') { @@ -275,4 +319,57 @@ private function normalizeBackslash(string $class): string return ltrim($class, '\\'); } + /** + * @return SymbolKind::CLASSLIKE|SymbolKind::FUNCTION + */ + private function getFqnSymbolKind( + int $pointerBeforeName, + int $pointerAfterName, + bool $inAttribute + ): int + { + if ($inAttribute) { + return SymbolKind::CLASSLIKE; + } + + do { + $tokenBeforeName = $this->tokens[$pointerBeforeName]; + + if (!is_array($tokenBeforeName)) { + break; + } + + if ($tokenBeforeName[0] === T_WHITESPACE || $tokenBeforeName[0] === T_COMMENT || $tokenBeforeName[0] === T_DOC_COMMENT) { + $pointerBeforeName--; + continue; + } + + break; + } while ($pointerBeforeName >= 0); + + do { + $tokenAfterName = $this->tokens[$pointerAfterName]; + + if (!is_array($tokenAfterName)) { + break; + } + + if ($tokenAfterName[0] === T_WHITESPACE || $tokenAfterName[0] === T_COMMENT || $tokenAfterName[0] === T_DOC_COMMENT) { + $pointerAfterName++; + continue; + } + + break; + } while ($pointerAfterName < $this->numTokens); + + if ( + $tokenAfterName === '(' + && $tokenBeforeName[0] !== T_NEW // eliminate new \ClassName( + ) { + return SymbolKind::FUNCTION; + } + + return SymbolKind::CLASSLIKE; // constant may fall here, this is eliminated later + } + } diff --git a/site/vendor/spaze/phpstan-disallowed-calls/composer.json b/site/vendor/spaze/phpstan-disallowed-calls/composer.json index f04308713..66164ef09 100644 --- a/site/vendor/spaze/phpstan-disallowed-calls/composer.json +++ b/site/vendor/spaze/phpstan-disallowed-calls/composer.json @@ -26,8 +26,8 @@ }, "require-dev": { "nette/neon": "^3.2", - "nikic/php-parser": "^4.13", - "phpunit/phpunit": "^8.5 || ^10.1", + "nikic/php-parser": "^4.13 || ^5.0", + "phpunit/phpunit": "^8.5 || ^10.1 || ^11.0", "php-parallel-lint/php-parallel-lint": "^1.2", "php-parallel-lint/php-console-highlighter": "^1.0", "spaze/coding-standard": "^1.7" diff --git a/site/vendor/spaze/phpstan-disallowed-calls/disallowed-dangerous-calls.neon b/site/vendor/spaze/phpstan-disallowed-calls/disallowed-dangerous-calls.neon index 12c7e34d4..4f53d0fb8 100644 --- a/site/vendor/spaze/phpstan-disallowed-calls/disallowed-dangerous-calls.neon +++ b/site/vendor/spaze/phpstan-disallowed-calls/disallowed-dangerous-calls.neon @@ -58,3 +58,7 @@ parameters: message: 'use some logger instead' allowParamsAnywhere: 2: true + - + function: 'phpinfo()' + message: 'might reveal session id or other tokens in cookies' + errorTip: 'see https://www.michalspacek.com/stealing-session-ids-with-phpinfo-and-how-to-stop-it and use e.g. spaze/phpinfo instead' diff --git a/site/vendor/spaze/phpstan-disallowed-calls/src/Allowed/Allowed.php b/site/vendor/spaze/phpstan-disallowed-calls/src/Allowed/Allowed.php index 8fdd40186..dad0840ff 100644 --- a/site/vendor/spaze/phpstan-disallowed-calls/src/Allowed/Allowed.php +++ b/site/vendor/spaze/phpstan-disallowed-calls/src/Allowed/Allowed.php @@ -14,6 +14,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use PHPStan\Type\VerbosityLevel; use Spaze\PHPStan\Rules\Disallowed\DisallowedWithParams; use Spaze\PHPStan\Rules\Disallowed\Exceptions\UnsupportedParamTypeException; use Spaze\PHPStan\Rules\Disallowed\Exceptions\UnsupportedParamTypeInConfigException; @@ -25,6 +26,7 @@ use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueCaseInsensitiveExcept; use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueExcept; use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueExceptAny; +use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueFlag; use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueFlagExcept; use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueFlagSpecific; use Spaze\PHPStan\Rules\Disallowed\Params\ParamValueSpecific; @@ -302,6 +304,13 @@ private function paramFactory(string $class, $key, $value): ParamValue } else { throw new UnsupportedParamTypeInConfigException($paramPosition, $paramName, gettype($paramValue)); } + if (is_subclass_of($class, ParamValueFlag::class)) { + foreach ($type->getConstantScalarValues() as $value) { + if (!is_int($value)) { + throw new UnsupportedParamTypeInConfigException($paramPosition, $paramName, gettype($value) . ' of ' . $type->describe(VerbosityLevel::precise())); + } + } + } return new $class($paramPosition, $paramName, $type); } diff --git a/site/vendor/spaze/phpstan-disallowed-calls/src/DisallowedCallFactory.php b/site/vendor/spaze/phpstan-disallowed-calls/src/DisallowedCallFactory.php index a352a7c23..907f564ca 100644 --- a/site/vendor/spaze/phpstan-disallowed-calls/src/DisallowedCallFactory.php +++ b/site/vendor/spaze/phpstan-disallowed-calls/src/DisallowedCallFactory.php @@ -50,7 +50,7 @@ public function createFromConfig(array $config): array foreach ((array)($disallowed['exclude'] ?? []) as $exclude) { $excludes[] = $this->normalizer->normalizeCall($exclude); } - $calls = array_values((array)$calls); + $calls = (array)$calls; try { foreach ($calls as $call) { $disallowedCall = new DisallowedCall( diff --git a/site/vendor/spaze/phpstan-disallowed-calls/src/Params/ParamValueFlag.php b/site/vendor/spaze/phpstan-disallowed-calls/src/Params/ParamValueFlag.php index d48fda88d..397aea9f4 100644 --- a/site/vendor/spaze/phpstan-disallowed-calls/src/Params/ParamValueFlag.php +++ b/site/vendor/spaze/phpstan-disallowed-calls/src/Params/ParamValueFlag.php @@ -5,16 +5,13 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Type; -use PHPStan\Type\VerbosityLevel; use Spaze\PHPStan\Rules\Disallowed\Exceptions\UnsupportedParamTypeException; -use Spaze\PHPStan\Rules\Disallowed\Exceptions\UnsupportedParamTypeInConfigException; abstract class ParamValueFlag extends ParamValue { /** * @throws UnsupportedParamTypeException - * @throws UnsupportedParamTypeInConfigException */ protected function isFlagSet(Type $type): bool { @@ -22,10 +19,7 @@ protected function isFlagSet(Type $type): bool throw new UnsupportedParamTypeException(); } foreach ($this->getType()->getConstantScalarValues() as $value) { - if (!is_int($value)) { - throw new UnsupportedParamTypeInConfigException($this->getPosition(), $this->getName(), gettype($value) . ' of ' . $this->getType()->describe(VerbosityLevel::precise())); - } - if (($value & $type->getValue()) !== 0) { + if (is_int($value) && ($value & $type->getValue()) !== 0) { return true; } }