diff --git a/composer.lock b/composer.lock index d4f816d21..6d33b0a40 100644 --- a/composer.lock +++ b/composer.lock @@ -155,16 +155,16 @@ }, { "name": "brick/math", - "version": "0.14.0", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { @@ -203,7 +203,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.0" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -211,7 +211,7 @@ "type": "github" } ], - "time": "2025-08-29T12:40:03+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { "name": "bshaffer/oauth2-server-php", @@ -277,16 +277,16 @@ }, { "name": "chillerlan/php-qrcode", - "version": "5.0.4", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43" + "reference": "7b66282572fc14075c0507d74d9837dab25b38d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/390393e97a6e42ccae0e0d6205b8d4200f7ddc43", - "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/7b66282572fc14075c0507d74d9837dab25b38d6", + "reference": "7b66282572fc14075c0507d74d9837dab25b38d6", "shasum": "" }, "require": { @@ -297,7 +297,7 @@ "require-dev": { "chillerlan/php-authenticator": "^4.3.1 || ^5.2.1", "ext-fileinfo": "*", - "phan/phan": "^5.5.1", + "phan/phan": "^5.5.2", "phpcompatibility/php-compatibility": "10.x-dev", "phpmd/phpmd": "^2.15", "phpunit/phpunit": "^9.6", @@ -366,7 +366,7 @@ "type": "Ko-Fi" } ], - "time": "2025-09-19T17:30:27+00:00" + "time": "2025-11-23T23:51:44+00:00" }, { "name": "chillerlan/php-settings-container", @@ -1014,33 +1014,38 @@ }, { "name": "league/uri", - "version": "7.5.1", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "f625804987a0a9112d954f9209d91fec52182344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", + "reference": "f625804987a0a9112d954f9209d91fec52182344", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.6", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -1068,6 +1073,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -1080,9 +1086,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -1092,7 +1100,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.6.0" }, "funding": [ { @@ -1100,26 +1108,25 @@ "type": "github" } ], - "time": "2024-12-08T08:40:02+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "league/uri-interfaces", - "version": "7.5.0", + "version": "7.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -1127,6 +1134,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -1151,7 +1159,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -1176,7 +1184,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" }, "funding": [ { @@ -1184,7 +1192,7 @@ "type": "github" } ], - "time": "2024-12-08T08:18:47+00:00" + "time": "2025-11-18T12:17:23+00:00" }, { "name": "lukasreschke/id3parser", @@ -1228,16 +1236,16 @@ }, { "name": "macgirvin/http-message-signer", - "version": "v0.2.8", + "version": "v0.2.12", "source": { "type": "git", "url": "https://github.com/macgirvin/HTTP-Message-Signer.git", - "reference": "8d70246ff06a6d2128d37069e7ea801f4b623dad" + "reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/8d70246ff06a6d2128d37069e7ea801f4b623dad", - "reference": "8d70246ff06a6d2128d37069e7ea801f4b623dad", + "url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/6e5b25a5536576e5046f5b0a7db620b5eb451453", + "reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453", "shasum": "" }, "require": { @@ -1266,9 +1274,9 @@ "description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests", "support": { "issues": "https://github.com/macgirvin/HTTP-Message-Signer/issues", - "source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.8" + "source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.12" }, - "time": "2025-10-07T19:56:11+00:00" + "time": "2025-12-02T18:48:05+00:00" }, { "name": "michelf/php-markdown", @@ -2830,50 +2838,45 @@ }, { "name": "scssphp/scssphp", - "version": "v2.0.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/scssphp/scssphp.git", - "reference": "024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f" + "reference": "d8450c2baf5fb07d00374999d0ea51276974d1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scssphp/scssphp/zipball/024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f", - "reference": "024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f", + "url": "https://api.github.com/repos/scssphp/scssphp/zipball/d8450c2baf5fb07d00374999d0ea51276974d1b6", + "reference": "d8450c2baf5fb07d00374999d0ea51276974d1b6", "shasum": "" }, "require": { "ext-ctype": "*", "ext-json": "*", - "league/uri": "^7.4", - "league/uri-interfaces": "^7.4", + "ext-mbstring": "*", + "league/uri": "^7.6", + "league/uri-interfaces": "^7.6", "php": ">=8.1", - "scssphp/source-span": "^1.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-mbstring": "^1.30" + "scssphp/source-span": "^1.1", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4", + "jgthms/bulma": "~0.9.4", + "jiripudil/phpstan-sealed-classes": "^1.3", + "phpstan/phpstan": "^2.1.31", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^9.5.6", "sass/sass-spec": "*", - "squizlabs/php_codesniffer": "~3.5", - "symfony/phpunit-bridge": "^5.1", - "symfony/var-dumper": "^6.3", + "squizlabs/php_codesniffer": "^3.13", + "symfony/phpunit-bridge": "^7.3 || ^8.0", + "symfony/polyfill-php84": "^1.33", + "symfony/var-dumper": "^6.4 || ^7.3 || ^8.0", "thoughtbot/bourbon": "^7.0", - "twbs/bootstrap": "~5.0", + "twbs/bootstrap": "^5.3", "twbs/bootstrap4": "4.6.1", "zurb/foundation": "~6.7.0" }, - "suggest": { - "ext-mbstring": "For best performance, mbstring should be installed as it is faster than the polyfill" - }, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": false, - "forward-command": false - } - }, "autoload": { "psr-4": { "ScssPhp\\ScssPhp\\": "src/" @@ -2896,7 +2899,7 @@ } ], "description": "scssphp is a compiler for SCSS written in PHP.", - "homepage": "http://scssphp.github.io/scssphp/", + "homepage": "https://scssphp.github.io/scssphp/", "keywords": [ "css", "less", @@ -2906,27 +2909,28 @@ ], "support": { "issues": "https://github.com/scssphp/scssphp/issues", - "source": "https://github.com/scssphp/scssphp/tree/v2.0.1" + "source": "https://github.com/scssphp/scssphp/tree/v2.1.0" }, - "time": "2025-01-31T12:28:20+00:00" + "time": "2025-11-21T17:27:59+00:00" }, { "name": "scssphp/source-span", - "version": "v1.0.0", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/scssphp/source-span.git", - "reference": "f08fc78765e6fb6fa8ca0573fc61b3f8860f0114" + "reference": "37d653206daf11da1ee60b333984101bc4c27ba2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scssphp/source-span/zipball/f08fc78765e6fb6fa8ca0573fc61b3f8860f0114", - "reference": "f08fc78765e6fb6fa8ca0573fc61b3f8860f0114", + "url": "https://api.github.com/repos/scssphp/source-span/zipball/37d653206daf11da1ee60b333984101bc4c27ba2", + "reference": "37d653206daf11da1ee60b333984101bc4c27ba2", "shasum": "" }, "require": { - "league/uri": "^7.4", - "league/uri-interfaces": "^7.4", + "ext-mbstring": "*", + "league/uri": "^7.6", + "league/uri-interfaces": "^7.6", "php": ">=8.1" }, "require-dev": { @@ -2934,8 +2938,8 @@ "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^9.5.6", "squizlabs/php_codesniffer": "~3.5", - "symfony/phpunit-bridge": "^5.1", - "symfony/var-dumper": "^6.3" + "symfony/phpunit-bridge": "^6.4 || ^7.3 || ^8.0", + "symfony/var-dumper": "^6.4 || ^7.3 || ^8.0" }, "type": "library", "extra": { @@ -2964,9 +2968,9 @@ ], "support": { "issues": "https://github.com/scssphp/source-span/issues", - "source": "https://github.com/scssphp/source-span/tree/v1.0.0" + "source": "https://github.com/scssphp/source-span/tree/v1.1.0" }, - "time": "2024-12-09T23:08:15+00:00" + "time": "2025-11-21T16:28:19+00:00" }, { "name": "simplepie/simplepie", @@ -3329,16 +3333,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.3.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { @@ -3347,7 +3351,7 @@ "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -3375,7 +3379,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.6" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { @@ -3395,7 +3399,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T09:52:27+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4406,16 +4410,16 @@ }, { "name": "php-mock/php-mock-phpunit", - "version": "2.13.1", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "29f90fe44a04105959d6ae835b10c9e0da2fcaa7" + "reference": "c074f7a260cb80bdc7cf0823dc23174bc49064e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/29f90fe44a04105959d6ae835b10c9e0da2fcaa7", - "reference": "29f90fe44a04105959d6ae835b10c9e0da2fcaa7", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/c074f7a260cb80bdc7cf0823dc23174bc49064e1", + "reference": "c074f7a260cb80bdc7cf0823dc23174bc49064e1", "shasum": "" }, "require": { @@ -4462,7 +4466,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.13.1" + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.14.0" }, "funding": [ { @@ -4470,7 +4474,7 @@ "type": "github" } ], - "time": "2025-09-23T06:00:08+00:00" + "time": "2025-11-19T21:07:31+00:00" }, { "name": "phpmd/phpmd", @@ -4557,11 +4561,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.31", + "version": "2.1.32", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", - "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", "shasum": "" }, "require": { @@ -4606,7 +4610,7 @@ "type": "github" } ], - "time": "2025-10-10T14:14:11+00:00" + "time": "2025-11-11T15:18:17+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4931,16 +4935,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.58", + "version": "10.5.59", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca" + "reference": "c47fe00df06fb1f68399ef7386edb01c25132473" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca", - "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c47fe00df06fb1f68399ef7386edb01c25132473", + "reference": "c47fe00df06fb1f68399ef7386edb01c25132473", "shasum": "" }, "require": { @@ -5012,7 +5016,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.59" }, "funding": [ { @@ -5036,7 +5040,7 @@ "type": "tidelift" } ], - "time": "2025-09-28T12:04:46+00:00" + "time": "2025-12-01T07:37:23+00:00" }, { "name": "psr/container", @@ -6046,16 +6050,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "06113cfdaf117fc2165f9cd040bd0f17fcd5242d" + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/06113cfdaf117fc2165f9cd040bd0f17fcd5242d", - "reference": "06113cfdaf117fc2165f9cd040bd0f17fcd5242d", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { @@ -6121,26 +6125,26 @@ "type": "thanks_dev" } ], - "time": "2025-09-15T11:28:58+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { "name": "symfony/config", - "version": "v7.3.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7" + "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7", - "reference": "9d18eba95655a3152ae4c1d53c6cc34eb4d4a0b7", + "url": "https://api.github.com/repos/symfony/config/zipball/f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", + "reference": "f76c74e93bce2b9285f2dad7fbd06fa8182a7a41", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^7.1", + "symfony/filesystem": "^7.1|^8.0", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -6148,11 +6152,11 @@ "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6180,7 +6184,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.3.6" + "source": "https://github.com/symfony/config/tree/v7.4.0" }, "funding": [ { @@ -6200,28 +6204,28 @@ "type": "tidelift" } ], - "time": "2025-11-02T08:04:43+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.3.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69" + "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69", - "reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/3972ca7bbd649467b21a54870721b9e9f3652f9b", + "reference": "3972ca7bbd649467b21a54870721b9e9f3652f9b", "shasum": "" }, "require": { "php": ">=8.2", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^3.5", - "symfony/var-exporter": "^6.4.20|^7.2.5" + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^6.4.20|^7.2.5|^8.0" }, "conflict": { "ext-psr": "<1.1|>=2", @@ -6234,9 +6238,9 @@ "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6264,7 +6268,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.3.6" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.0" }, "funding": [ { @@ -6284,7 +6288,7 @@ "type": "tidelift" } ], - "time": "2025-10-31T10:11:11+00:00" + "time": "2025-11-27T13:27:24+00:00" }, { "name": "symfony/service-contracts", @@ -6375,16 +6379,16 @@ }, { "name": "symfony/var-exporter", - "version": "v7.3.4", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4" + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", - "reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", + "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", "shasum": "" }, "require": { @@ -6392,9 +6396,9 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -6432,7 +6436,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.3.4" + "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" }, "funding": [ { @@ -6452,20 +6456,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-09-11T10:15:23+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -6494,7 +6498,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -6502,7 +6506,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], diff --git a/vendor/brick/math/CHANGELOG.md b/vendor/brick/math/CHANGELOG.md index 377cdae22..280ab5beb 100644 --- a/vendor/brick/math/CHANGELOG.md +++ b/vendor/brick/math/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## [0.14.1](https://github.com/brick/math/releases/tag/0.14.1) - 2025-11-24 + +✨ **New features** + +- New method: `BigNumber::ofNullable()` (#94 by @mrkh995) + +✨ **Compatibility fixes** + +- Fixed warnings on PHP 8.5 (#101 and #102 by @julien-boudry) + ## [0.14.0](https://github.com/brick/math/releases/tag/0.14.0) - 2025-08-29 ✨ **New features** diff --git a/vendor/brick/math/src/BigDecimal.php b/vendor/brick/math/src/BigDecimal.php index 1a540c7d6..6d264b1fd 100644 --- a/vendor/brick/math/src/BigDecimal.php +++ b/vendor/brick/math/src/BigDecimal.php @@ -9,8 +9,19 @@ use Brick\Math\Exception\MathException; use Brick\Math\Exception\NegativeNumberException; use Brick\Math\Internal\Calculator; use Brick\Math\Internal\CalculatorRegistry; +use InvalidArgumentException; +use LogicException; use Override; +use function rtrim; +use function sprintf; +use function str_pad; +use function str_repeat; +use function strlen; +use function substr; + +use const STR_PAD_LEFT; + /** * Immutable, arbitrary-precision signed decimal numbers. */ @@ -46,12 +57,6 @@ final readonly class BigDecimal extends BigNumber $this->scale = $scale; } - #[Override] - protected static function from(BigNumber $number): static - { - return $number->toBigDecimal(); - } - /** * Creates a BigDecimal from an unscaled value and a scale. * @@ -63,13 +68,13 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal + public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0): BigDecimal { $value = (string) BigInteger::of($value); if ($scale < 0) { if ($value !== '0') { - $value .= \str_repeat('0', -$scale); + $value .= str_repeat('0', -$scale); } $scale = 0; } @@ -82,7 +87,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public static function zero() : BigDecimal + public static function zero(): BigDecimal { /** @var BigDecimal|null $zero */ static $zero; @@ -99,7 +104,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public static function one() : BigDecimal + public static function one(): BigDecimal { /** @var BigDecimal|null $one */ static $one; @@ -116,7 +121,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public static function ten() : BigDecimal + public static function ten(): BigDecimal { /** @var BigDecimal|null $ten */ static $ten; @@ -139,7 +144,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function plus(BigNumber|int|float|string $that) : BigDecimal + public function plus(BigNumber|int|float|string $that): BigDecimal { $that = BigDecimal::of($that); @@ -170,7 +175,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function minus(BigNumber|int|float|string $that) : BigDecimal + public function minus(BigNumber|int|float|string $that): BigDecimal { $that = BigDecimal::of($that); @@ -197,7 +202,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal + public function multipliedBy(BigNumber|int|float|string $that): BigDecimal { $that = BigDecimal::of($that); @@ -222,12 +227,12 @@ final readonly class BigDecimal extends BigNumber * @param int|null $scale The desired scale, or null to use the scale of this number. * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. * - * @throws \InvalidArgumentException If the scale or rounding mode is invalid. - * @throws MathException If the number is invalid, is zero, or rounding was necessary. + * @throws InvalidArgumentException If the scale or rounding mode is invalid. + * @throws MathException If the number is invalid, is zero, or rounding was necessary. * * @pure */ - public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal { $that = BigDecimal::of($that); @@ -238,7 +243,7 @@ final readonly class BigDecimal extends BigNumber if ($scale === null) { $scale = $this->scale; } elseif ($scale < 0) { - throw new \InvalidArgumentException('Scale cannot be negative.'); + throw new InvalidArgumentException('Scale cannot be negative.'); } if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) { @@ -265,7 +270,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal + public function exactlyDividedBy(BigNumber|int|float|string $that): BigDecimal { $that = BigDecimal::of($that); @@ -275,13 +280,13 @@ final readonly class BigDecimal extends BigNumber [, $b] = $this->scaleValues($this, $that); - $d = \rtrim($b, '0'); - $scale = \strlen($b) - \strlen($d); + $d = rtrim($b, '0'); + $scale = strlen($b) - strlen($d); $calculator = CalculatorRegistry::get(); foreach ([5, 2] as $prime) { - for (;;) { + for (; ;) { $lastDigit = (int) $d[-1]; if ($lastDigit % $prime !== 0) { @@ -308,13 +313,14 @@ final readonly class BigDecimal extends BigNumber * * @throws MathException If min/max are not convertible to a BigDecimal. */ - public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigDecimal + public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): BigDecimal { if ($this->isLessThan($min)) { return BigDecimal::of($min); } elseif ($this->isGreaterThan($max)) { return BigDecimal::of($max); } + return $this; } @@ -323,11 +329,11 @@ final readonly class BigDecimal extends BigNumber * * The result has a scale of `$this->scale * $exponent`. * - * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. + * @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. * * @pure */ - public function power(int $exponent) : BigDecimal + public function power(int $exponent): BigDecimal { if ($exponent === 0) { return BigDecimal::one(); @@ -338,10 +344,10 @@ final readonly class BigDecimal extends BigNumber } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { - throw new \InvalidArgumentException(\sprintf( + throw new InvalidArgumentException(sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, - Calculator::MAX_POWER + Calculator::MAX_POWER, )); } @@ -359,7 +365,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function quotient(BigNumber|int|float|string $that) : BigDecimal + public function quotient(BigNumber|int|float|string $that): BigDecimal { $that = BigDecimal::of($that); @@ -386,7 +392,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function remainder(BigNumber|int|float|string $that) : BigDecimal + public function remainder(BigNumber|int|float|string $that): BigDecimal { $that = BigDecimal::of($that); @@ -417,7 +423,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function quotientAndRemainder(BigNumber|int|float|string $that) : array + public function quotientAndRemainder(BigNumber|int|float|string $that): array { $that = BigDecimal::of($that); @@ -441,15 +447,15 @@ final readonly class BigDecimal extends BigNumber /** * Returns the square root of this number, rounded down to the given number of decimals. * - * @throws \InvalidArgumentException If the scale is negative. - * @throws NegativeNumberException If this number is negative. + * @throws InvalidArgumentException If the scale is negative. + * @throws NegativeNumberException If this number is negative. * * @pure */ - public function sqrt(int $scale) : BigDecimal + public function sqrt(int $scale): BigDecimal { if ($scale < 0) { - throw new \InvalidArgumentException('Scale cannot be negative.'); + throw new InvalidArgumentException('Scale cannot be negative.'); } if ($this->value === '0') { @@ -465,15 +471,15 @@ final readonly class BigDecimal extends BigNumber if ($addDigits > 0) { // add zeros - $value .= \str_repeat('0', $addDigits); + $value .= str_repeat('0', $addDigits); } elseif ($addDigits < 0) { // trim digits - if (-$addDigits >= \strlen($this->value)) { + if (-$addDigits >= strlen($this->value)) { // requesting a scale too low, will always yield a zero result return new BigDecimal('0', $scale); } - $value = \substr($value, 0, $addDigits); + $value = substr($value, 0, $addDigits); } $value = CalculatorRegistry::get()->sqrt($value); @@ -486,7 +492,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function withPointMovedLeft(int $n) : BigDecimal + public function withPointMovedLeft(int $n): BigDecimal { if ($n === 0) { return $this; @@ -504,7 +510,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function withPointMovedRight(int $n) : BigDecimal + public function withPointMovedRight(int $n): BigDecimal { if ($n === 0) { return $this; @@ -519,7 +525,7 @@ final readonly class BigDecimal extends BigNumber if ($scale < 0) { if ($value !== '0') { - $value .= \str_repeat('0', -$scale); + $value .= str_repeat('0', -$scale); } $scale = 0; } @@ -532,19 +538,19 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function stripTrailingZeros() : BigDecimal + public function stripTrailingZeros(): BigDecimal { if ($this->scale === 0) { return $this; } - $trimmedValue = \rtrim($this->value, '0'); + $trimmedValue = rtrim($this->value, '0'); if ($trimmedValue === '') { return BigDecimal::zero(); } - $trimmableZeros = \strlen($this->value) - \strlen($trimmedValue); + $trimmableZeros = strlen($this->value) - strlen($trimmedValue); if ($trimmableZeros === 0) { return $this; @@ -554,7 +560,7 @@ final readonly class BigDecimal extends BigNumber $trimmableZeros = $this->scale; } - $value = \substr($this->value, 0, -$trimmableZeros); + $value = substr($this->value, 0, -$trimmableZeros); $scale = $this->scale - $trimmableZeros; return new BigDecimal($value, $scale); @@ -565,7 +571,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function abs() : BigDecimal + public function abs(): BigDecimal { return $this->isNegative() ? $this->negated() : $this; } @@ -575,13 +581,13 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function negated() : BigDecimal + public function negated(): BigDecimal { return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale); } #[Override] - public function compareTo(BigNumber|int|float|string $that) : int + public function compareTo(BigNumber|int|float|string $that): int { $that = BigNumber::of($that); @@ -595,11 +601,11 @@ final readonly class BigDecimal extends BigNumber return CalculatorRegistry::get()->cmp($a, $b); } - return - $that->compareTo($this); + return -$that->compareTo($this); } #[Override] - public function getSign() : int + public function getSign(): int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } @@ -607,7 +613,7 @@ final readonly class BigDecimal extends BigNumber /** * @pure */ - public function getUnscaledValue() : BigInteger + public function getUnscaledValue(): BigInteger { return self::newBigInteger($this->value); } @@ -615,7 +621,7 @@ final readonly class BigDecimal extends BigNumber /** * @pure */ - public function getScale() : int + public function getScale(): int { return $this->scale; } @@ -644,7 +650,7 @@ final readonly class BigDecimal extends BigNumber return 0; } - $length = \strlen($value); + $length = strlen($value); return ($value[0] === '-') ? $length - 1 : $length; } @@ -656,7 +662,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function getIntegralPart() : string + public function getIntegralPart(): string { if ($this->scale === 0) { return $this->value; @@ -664,7 +670,7 @@ final readonly class BigDecimal extends BigNumber $value = $this->getUnscaledValueWithLeadingZeros(); - return \substr($value, 0, -$this->scale); + return substr($value, 0, -$this->scale); } /** @@ -676,7 +682,7 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function getFractionalPart() : string + public function getFractionalPart(): string { if ($this->scale === 0) { return ''; @@ -684,7 +690,7 @@ final readonly class BigDecimal extends BigNumber $value = $this->getUnscaledValueWithLeadingZeros(); - return \substr($value, -$this->scale); + return substr($value, -$this->scale); } /** @@ -692,13 +698,13 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - public function hasNonZeroFractionalPart() : bool + public function hasNonZeroFractionalPart(): bool { - return $this->getFractionalPart() !== \str_repeat('0', $this->scale); + return $this->getFractionalPart() !== str_repeat('0', $this->scale); } #[Override] - public function toBigInteger() : BigInteger + public function toBigInteger(): BigInteger { $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); @@ -706,22 +712,22 @@ final readonly class BigDecimal extends BigNumber } #[Override] - public function toBigDecimal() : BigDecimal + public function toBigDecimal(): BigDecimal { return $this; } #[Override] - public function toBigRational() : BigRational + public function toBigRational(): BigRational { $numerator = self::newBigInteger($this->value); - $denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale)); + $denominator = self::newBigInteger('1' . str_repeat('0', $this->scale)); return self::newBigRational($numerator, $denominator, false); } #[Override] - public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal { if ($scale === $this->scale) { return $this; @@ -731,13 +737,13 @@ final readonly class BigDecimal extends BigNumber } #[Override] - public function toInt() : int + public function toInt(): int { return $this->toBigInteger()->toInt(); } #[Override] - public function toFloat() : float + public function toFloat(): float { return (float) (string) $this; } @@ -746,7 +752,7 @@ final readonly class BigDecimal extends BigNumber * @return numeric-string */ #[Override] - public function __toString() : string + public function __toString(): string { if ($this->scale === 0) { /** @var numeric-string */ @@ -756,7 +762,7 @@ final readonly class BigDecimal extends BigNumber $value = $this->getUnscaledValueWithLeadingZeros(); /** @phpstan-ignore return.type */ - return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale); + return substr($value, 0, -$this->scale) . '.' . substr($value, -$this->scale); } /** @@ -778,13 +784,13 @@ final readonly class BigDecimal extends BigNumber * * @param array{value: string, scale: int} $data * - * @throws \LogicException + * @throws LogicException */ public function __unserialize(array $data): void { /** @phpstan-ignore isset.initializedProperty */ if (isset($this->value)) { - throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); + throw new LogicException('__unserialize() is an internal function, it must not be called directly.'); } /** @phpstan-ignore deadCode.unreachable */ @@ -792,6 +798,12 @@ final readonly class BigDecimal extends BigNumber $this->scale = $data['scale']; } + #[Override] + protected static function from(BigNumber $number): static + { + return $number->toBigDecimal(); + } + /** * Puts the internal values of the given decimal numbers on the same scale. * @@ -799,15 +811,15 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - private function scaleValues(BigDecimal $x, BigDecimal $y) : array + private function scaleValues(BigDecimal $x, BigDecimal $y): array { $a = $x->value; $b = $y->value; if ($b !== '0' && $x->scale > $y->scale) { - $b .= \str_repeat('0', $x->scale - $y->scale); + $b .= str_repeat('0', $x->scale - $y->scale); } elseif ($a !== '0' && $x->scale < $y->scale) { - $a .= \str_repeat('0', $y->scale - $x->scale); + $a .= str_repeat('0', $y->scale - $x->scale); } return [$a, $b]; @@ -816,12 +828,12 @@ final readonly class BigDecimal extends BigNumber /** * @pure */ - private function valueWithMinScale(int $scale) : string + private function valueWithMinScale(int $scale): string { $value = $this->value; if ($this->value !== '0' && $scale > $this->scale) { - $value .= \str_repeat('0', $scale - $this->scale); + $value .= str_repeat('0', $scale - $this->scale); } return $value; @@ -832,12 +844,12 @@ final readonly class BigDecimal extends BigNumber * * @pure */ - private function getUnscaledValueWithLeadingZeros() : string + private function getUnscaledValueWithLeadingZeros(): string { $value = $this->value; $targetLength = $this->scale + 1; $negative = ($value[0] === '-'); - $length = \strlen($value); + $length = strlen($value); if ($negative) { $length--; @@ -848,10 +860,10 @@ final readonly class BigDecimal extends BigNumber } if ($negative) { - $value = \substr($value, 1); + $value = substr($value, 1); } - $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT); + $value = str_pad($value, $targetLength, '0', STR_PAD_LEFT); if ($negative) { $value = '-' . $value; diff --git a/vendor/brick/math/src/BigInteger.php b/vendor/brick/math/src/BigInteger.php index 48a7bb3cf..9dd29dfc2 100644 --- a/vendor/brick/math/src/BigInteger.php +++ b/vendor/brick/math/src/BigInteger.php @@ -11,8 +11,30 @@ use Brick\Math\Exception\NegativeNumberException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Internal\Calculator; use Brick\Math\Internal\CalculatorRegistry; +use InvalidArgumentException; +use LogicException; use Override; +use function assert; +use function bin2hex; +use function chr; +use function filter_var; +use function hex2bin; +use function in_array; +use function intdiv; +use function ltrim; +use function ord; +use function preg_match; +use function preg_quote; +use function random_bytes; +use function sprintf; +use function str_repeat; +use function strlen; +use function strtolower; +use function substr; + +use const FILTER_VALIDATE_INT; + /** * An arbitrary-size integer. * @@ -41,12 +63,6 @@ final readonly class BigInteger extends BigNumber $this->value = $value; } - #[Override] - protected static function from(BigNumber $number): static - { - return $number->toBigInteger(); - } - /** * Creates a number from a string in a given base. * @@ -61,27 +77,27 @@ final readonly class BigInteger extends BigNumber * @param string $number The number to convert, in the given base. * @param int $base The base of the number, between 2 and 36. * - * @throws NumberFormatException If the number is empty, or contains invalid chars for the given base. - * @throws \InvalidArgumentException If the base is out of range. + * @throws NumberFormatException If the number is empty, or contains invalid chars for the given base. + * @throws InvalidArgumentException If the base is out of range. * * @pure */ - public static function fromBase(string $number, int $base) : BigInteger + public static function fromBase(string $number, int $base): BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } if ($base < 2 || $base > 36) { - throw new \InvalidArgumentException(\sprintf('Base %d is not in range 2 to 36.', $base)); + throw new InvalidArgumentException(sprintf('Base %d is not in range 2 to 36.', $base)); } if ($number[0] === '-') { $sign = '-'; - $number = \substr($number, 1); + $number = substr($number, 1); } elseif ($number[0] === '+') { $sign = ''; - $number = \substr($number, 1); + $number = substr($number, 1); } else { $sign = ''; } @@ -90,7 +106,7 @@ final readonly class BigInteger extends BigNumber throw new NumberFormatException('The number cannot be empty.'); } - $number = \ltrim($number, '0'); + $number = ltrim($number, '0'); if ($number === '') { // The result will be the same in any base, avoid further calculation. @@ -102,10 +118,10 @@ final readonly class BigInteger extends BigNumber return new BigInteger($sign . '1'); } - $pattern = '/[^' . \substr(Calculator::ALPHABET, 0, $base) . ']/'; + $pattern = '/[^' . substr(Calculator::ALPHABET, 0, $base) . ']/'; - if (\preg_match($pattern, \strtolower($number), $matches) === 1) { - throw new NumberFormatException(\sprintf('"%s" is not a valid character in base %d.', $matches[0], $base)); + if (preg_match($pattern, strtolower($number), $matches) === 1) { + throw new NumberFormatException(sprintf('"%s" is not a valid character in base %d.', $matches[0], $base)); } if ($base === 10) { @@ -126,26 +142,26 @@ final readonly class BigInteger extends BigNumber * @param string $number The number to parse. * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * - * @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet. - * @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars. + * @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet. + * @throws InvalidArgumentException If the alphabet does not contain at least 2 chars. * * @pure */ - public static function fromArbitraryBase(string $number, string $alphabet) : BigInteger + public static function fromArbitraryBase(string $number, string $alphabet): BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } - $base = \strlen($alphabet); + $base = strlen($alphabet); if ($base < 2) { - throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); + throw new InvalidArgumentException('The alphabet must contain at least 2 chars.'); } - $pattern = '/[^' . \preg_quote($alphabet, '/') . ']/'; + $pattern = '/[^' . preg_quote($alphabet, '/') . ']/'; - if (\preg_match($pattern, $number, $matches) === 1) { + if (preg_match($pattern, $number, $matches) === 1) { throw NumberFormatException::charNotInAlphabet($matches[0]); } @@ -173,7 +189,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public static function fromBytes(string $value, bool $signed = true) : BigInteger + public static function fromBytes(string $value, bool $signed = true): BigInteger { if ($value === '') { throw new NumberFormatException('The byte string must not be empty.'); @@ -182,14 +198,14 @@ final readonly class BigInteger extends BigNumber $twosComplement = false; if ($signed) { - $x = \ord($value[0]); + $x = ord($value[0]); if (($twosComplement = ($x >= 0x80))) { $value = ~$value; } } - $number = self::fromBase(\bin2hex($value), 16); + $number = self::fromBase(bin2hex($value), 16); if ($twosComplement) { return $number->plus(1)->negated(); @@ -208,12 +224,12 @@ final readonly class BigInteger extends BigNumber * a string of random bytes of the given length. Defaults * to the `random_bytes()` function. * - * @throws \InvalidArgumentException If $numBits is negative. + * @throws InvalidArgumentException If $numBits is negative. */ - public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null) : BigInteger + public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null): BigInteger { if ($numBits < 0) { - throw new \InvalidArgumentException('The number of bits cannot be negative.'); + throw new InvalidArgumentException('The number of bits cannot be negative.'); } if ($numBits === 0) { @@ -225,12 +241,12 @@ final readonly class BigInteger extends BigNumber } /** @var int<1, max> $byteLength */ - $byteLength = \intdiv($numBits - 1, 8) + 1; + $byteLength = intdiv($numBits - 1, 8) + 1; $extraBits = ($byteLength * 8 - $numBits); - $bitmask = \chr(0xFF >> $extraBits); + $bitmask = chr(0xFF >> $extraBits); - $randomBytes = $randomBytesGenerator($byteLength); + $randomBytes = $randomBytesGenerator($byteLength); $randomBytes[0] = $randomBytes[0] & $bitmask; return self::fromBytes($randomBytes, false); @@ -253,8 +269,8 @@ final readonly class BigInteger extends BigNumber public static function randomRange( BigNumber|int|float|string $min, BigNumber|int|float|string $max, - ?callable $randomBytesGenerator = null - ) : BigInteger { + ?callable $randomBytesGenerator = null, + ): BigInteger { $min = BigInteger::of($min); $max = BigInteger::of($max); @@ -266,7 +282,7 @@ final readonly class BigInteger extends BigNumber return $min; } - $diff = $max->minus($min); + $diff = $max->minus($min); $bitLength = $diff->getBitLength(); // try until the number is in range (50% to 100% chance of success) @@ -282,7 +298,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public static function zero() : BigInteger + public static function zero(): BigInteger { /** @var BigInteger|null $zero */ static $zero; @@ -299,7 +315,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public static function one() : BigInteger + public static function one(): BigInteger { /** @var BigInteger|null $one */ static $one; @@ -316,7 +332,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public static function ten() : BigInteger + public static function ten(): BigInteger { /** @var BigInteger|null $ten */ static $ten; @@ -355,7 +371,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function plus(BigNumber|int|float|string $that) : BigInteger + public function plus(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -381,7 +397,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function minus(BigNumber|int|float|string $that) : BigInteger + public function minus(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -403,7 +419,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function multipliedBy(BigNumber|int|float|string $that) : BigInteger + public function multipliedBy(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -431,7 +447,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigInteger + public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigInteger { $that = BigInteger::of($that); @@ -460,25 +476,25 @@ final readonly class BigInteger extends BigNumber * * @throws MathException If min/max are not convertible to a BigInteger. */ - public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigInteger + public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): BigInteger { if ($this->isLessThan($min)) { return BigInteger::of($min); } elseif ($this->isGreaterThan($max)) { return BigInteger::of($max); } + return $this; } - /** * Returns this number exponentiated to the given value. * - * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. + * @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. * * @pure */ - public function power(int $exponent) : BigInteger + public function power(int $exponent): BigInteger { if ($exponent === 0) { return BigInteger::one(); @@ -489,10 +505,10 @@ final readonly class BigInteger extends BigNumber } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { - throw new \InvalidArgumentException(\sprintf( + throw new InvalidArgumentException(sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, - Calculator::MAX_POWER + Calculator::MAX_POWER, )); } @@ -508,7 +524,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function quotient(BigNumber|int|float|string $that) : BigInteger + public function quotient(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -536,7 +552,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function remainder(BigNumber|int|float|string $that) : BigInteger + public function remainder(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -564,7 +580,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function quotientAndRemainder(BigNumber|int|float|string $that) : array + public function quotientAndRemainder(BigNumber|int|float|string $that): array { $that = BigInteger::of($that); @@ -576,7 +592,7 @@ final readonly class BigInteger extends BigNumber return [ new BigInteger($quotient), - new BigInteger($remainder) + new BigInteger($remainder), ]; } @@ -594,7 +610,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function mod(BigNumber|int|float|string $that) : BigInteger + public function mod(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -617,7 +633,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function modInverse(BigInteger $m) : BigInteger + public function modInverse(BigInteger $m): BigInteger { if ($m->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); @@ -653,7 +669,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function modPow(BigNumber|int|float|string $exp, BigNumber|int|float|string $mod) : BigInteger + public function modPow(BigNumber|int|float|string $exp, BigNumber|int|float|string $mod): BigInteger { $exp = BigInteger::of($exp); $mod = BigInteger::of($mod); @@ -680,7 +696,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function gcd(BigNumber|int|float|string $that) : BigInteger + public function gcd(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -706,7 +722,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function sqrt() : BigInteger + public function sqrt(): BigInteger { if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); @@ -722,7 +738,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function abs() : BigInteger + public function abs(): BigInteger { return $this->isNegative() ? $this->negated() : $this; } @@ -732,7 +748,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function negated() : BigInteger + public function negated(): BigInteger { return new BigInteger(CalculatorRegistry::get()->neg($this->value)); } @@ -746,7 +762,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function and(BigNumber|int|float|string $that) : BigInteger + public function and(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -762,7 +778,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function or(BigNumber|int|float|string $that) : BigInteger + public function or(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -778,7 +794,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function xor(BigNumber|int|float|string $that) : BigInteger + public function xor(BigNumber|int|float|string $that): BigInteger { $that = BigInteger::of($that); @@ -790,7 +806,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function not() : BigInteger + public function not(): BigInteger { return $this->negated()->minus(1); } @@ -800,14 +816,14 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function shiftedLeft(int $distance) : BigInteger + public function shiftedLeft(int $distance): BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { - return $this->shiftedRight(- $distance); + return $this->shiftedRight(-$distance); } return $this->multipliedBy(BigInteger::of(2)->power($distance)); @@ -818,14 +834,14 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function shiftedRight(int $distance) : BigInteger + public function shiftedRight(int $distance): BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { - return $this->shiftedLeft(- $distance); + return $this->shiftedLeft(-$distance); } $operand = BigInteger::of(2)->power($distance); @@ -845,7 +861,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function getBitLength() : int + public function getBitLength(): int { if ($this->value === '0') { return 0; @@ -855,7 +871,7 @@ final readonly class BigInteger extends BigNumber return $this->abs()->minus(1)->getBitLength(); } - return \strlen($this->toBase(2)); + return strlen($this->toBase(2)); } /** @@ -865,7 +881,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function getLowestSetBit() : int + public function getLowestSetBit(): int { $n = $this; $bitLength = $this->getBitLength(); @@ -886,9 +902,9 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function isEven() : bool + public function isEven(): bool { - return \in_array($this->value[-1], ['0', '2', '4', '6', '8'], true); + return in_array($this->value[-1], ['0', '2', '4', '6', '8'], true); } /** @@ -896,9 +912,9 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function isOdd() : bool + public function isOdd(): bool { - return \in_array($this->value[-1], ['1', '3', '5', '7', '9'], true); + return in_array($this->value[-1], ['1', '3', '5', '7', '9'], true); } /** @@ -908,21 +924,21 @@ final readonly class BigInteger extends BigNumber * * @param int $n The bit to test, 0-based. * - * @throws \InvalidArgumentException If the bit to test is negative. + * @throws InvalidArgumentException If the bit to test is negative. * * @pure */ - public function testBit(int $n) : bool + public function testBit(int $n): bool { if ($n < 0) { - throw new \InvalidArgumentException('The bit to test cannot be negative.'); + throw new InvalidArgumentException('The bit to test cannot be negative.'); } return $this->shiftedRight($n)->isOdd(); } #[Override] - public function compareTo(BigNumber|int|float|string $that) : int + public function compareTo(BigNumber|int|float|string $that): int { $that = BigNumber::of($that); @@ -930,45 +946,45 @@ final readonly class BigInteger extends BigNumber return CalculatorRegistry::get()->cmp($this->value, $that->value); } - return - $that->compareTo($this); + return -$that->compareTo($this); } #[Override] - public function getSign() : int + public function getSign(): int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } #[Override] - public function toBigInteger() : BigInteger + public function toBigInteger(): BigInteger { return $this; } #[Override] - public function toBigDecimal() : BigDecimal + public function toBigDecimal(): BigDecimal { return self::newBigDecimal($this->value); } #[Override] - public function toBigRational() : BigRational + public function toBigRational(): BigRational { return self::newBigRational($this, BigInteger::one(), false); } #[Override] - public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal { return $this->toBigDecimal()->toScale($scale, $roundingMode); } #[Override] - public function toInt() : int + public function toInt(): int { - $intValue = (int) $this->value; + $intValue = filter_var($this->value, FILTER_VALIDATE_INT); - if ($this->value !== (string) $intValue) { + if ($intValue === false) { throw IntegerOverflowException::toIntOverflow($this); } @@ -976,7 +992,7 @@ final readonly class BigInteger extends BigNumber } #[Override] - public function toFloat() : float + public function toFloat(): float { return (float) $this->value; } @@ -986,18 +1002,18 @@ final readonly class BigInteger extends BigNumber * * The output will always be lowercase for bases greater than 10. * - * @throws \InvalidArgumentException If the base is out of range. + * @throws InvalidArgumentException If the base is out of range. * * @pure */ - public function toBase(int $base) : string + public function toBase(int $base): string { if ($base === 10) { return $this->value; } if ($base < 2 || $base > 36) { - throw new \InvalidArgumentException(\sprintf('Base %d is out of range [2, 36]', $base)); + throw new InvalidArgumentException(sprintf('Base %d is out of range [2, 36]', $base)); } return CalculatorRegistry::get()->toBase($this->value, $base); @@ -1011,17 +1027,17 @@ final readonly class BigInteger extends BigNumber * * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * - * @throws NegativeNumberException If this number is negative. - * @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars. + * @throws NegativeNumberException If this number is negative. + * @throws InvalidArgumentException If the given alphabet does not contain at least 2 chars. * * @pure */ - public function toArbitraryBase(string $alphabet) : string + public function toArbitraryBase(string $alphabet): string { - $base = \strlen($alphabet); + $base = strlen($alphabet); if ($base < 2) { - throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); + throw new InvalidArgumentException('The alphabet must contain at least 2 chars.'); } if ($this->value[0] === '-') { @@ -1051,7 +1067,7 @@ final readonly class BigInteger extends BigNumber * * @pure */ - public function toBytes(bool $signed = true) : string + public function toBytes(bool $signed = true): string { if (! $signed && $this->isNegative()) { throw new NegativeNumberException('Cannot convert a negative number to a byte string when $signed is false.'); @@ -1059,24 +1075,24 @@ final readonly class BigInteger extends BigNumber $hex = $this->abs()->toBase(16); - if (\strlen($hex) % 2 !== 0) { + if (strlen($hex) % 2 !== 0) { $hex = '0' . $hex; } - $baseHexLength = \strlen($hex); + $baseHexLength = strlen($hex); if ($signed) { if ($this->isNegative()) { - $bin = \hex2bin($hex); + $bin = hex2bin($hex); assert($bin !== false); - $hex = \bin2hex(~$bin); + $hex = bin2hex(~$bin); $hex = self::fromBase($hex, 16)->plus(1)->toBase(16); - $hexLength = \strlen($hex); + $hexLength = strlen($hex); if ($hexLength < $baseHexLength) { - $hex = \str_repeat('0', $baseHexLength - $hexLength) . $hex; + $hex = str_repeat('0', $baseHexLength - $hexLength) . $hex; } if ($hex[0] < '8') { @@ -1089,7 +1105,7 @@ final readonly class BigInteger extends BigNumber } } - $result = \hex2bin($hex); + $result = hex2bin($hex); assert($result !== false); return $result; @@ -1099,7 +1115,7 @@ final readonly class BigInteger extends BigNumber * @return numeric-string */ #[Override] - public function __toString() : string + public function __toString(): string { /** @var numeric-string */ return $this->value; @@ -1124,16 +1140,22 @@ final readonly class BigInteger extends BigNumber * * @param array{value: string} $data * - * @throws \LogicException + * @throws LogicException */ public function __unserialize(array $data): void { /** @phpstan-ignore isset.initializedProperty */ if (isset($this->value)) { - throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); + throw new LogicException('__unserialize() is an internal function, it must not be called directly.'); } /** @phpstan-ignore deadCode.unreachable */ $this->value = $data['value']; } + + #[Override] + protected static function from(BigNumber $number): static + { + return $number->toBigInteger(); + } } diff --git a/vendor/brick/math/src/BigNumber.php b/vendor/brick/math/src/BigNumber.php index 2fbdf696e..0e09e1316 100644 --- a/vendor/brick/math/src/BigNumber.php +++ b/vendor/brick/math/src/BigNumber.php @@ -8,7 +8,28 @@ use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Exception\RoundingNecessaryException; +use InvalidArgumentException; +use JsonSerializable; use Override; +use Stringable; + +use function array_shift; +use function assert; +use function filter_var; +use function is_float; +use function is_int; +use function is_nan; +use function is_null; +use function ltrim; +use function preg_match; +use function str_contains; +use function str_repeat; +use function strlen; +use function substr; + +use const FILTER_VALIDATE_INT; + +use const PREG_UNMATCHED_AS_NULL; /** * Base class for arbitrary-precision numbers. @@ -18,18 +39,18 @@ use Override; * * @phpstan-sealed BigInteger|BigDecimal|BigRational */ -abstract readonly class BigNumber implements \JsonSerializable, \Stringable +abstract readonly class BigNumber implements JsonSerializable, Stringable { /** * The regular expression used to parse integer or decimal numbers. */ private const PARSE_REGEXP_NUMERICAL = '/^' . - '(?[\-\+])?' . - '(?[0-9]+)?' . - '(?\.)?' . - '(?[0-9]+)?' . - '(?:[eE](?[\-\+]?[0-9]+))?' . + '(?[\-\+])?' . + '(?[0-9]+)?' . + '(?\.)?' . + '(?[0-9]+)?' . + '(?:[eE](?[\-\+]?[0-9]+))?' . '$/'; /** @@ -37,10 +58,10 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable */ private const PARSE_REGEXP_RATIONAL = '/^' . - '(?[\-\+])?' . - '(?[0-9]+)' . - '\/?' . - '(?[0-9]+)' . + '(?[\-\+])?' . + '(?[0-9]+)' . + '\/?' . + '(?[0-9]+)' . '$/'; /** @@ -59,13 +80,13 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable * When of() is called on BigInteger, BigDecimal, or BigRational, the resulting number is converted to an instance * of the subclass when possible; otherwise a RoundingNecessaryException exception is thrown. * - * @throws NumberFormatException If the format of the number is not valid. - * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. + * @throws NumberFormatException If the format of the number is not valid. + * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding. * * @pure */ - final public static function of(BigNumber|int|float|string $value) : static + final public static function of(BigNumber|int|float|string $value): static { $value = self::_of($value); @@ -79,36 +100,404 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable } /** - * @throws NumberFormatException If the format of the number is not valid. + * Creates a BigNumber of the given value, or returns null if the input is null. + * + * Behaves like of() for non-null values. + * + * @see BigNumber::of() + * + * @throws NumberFormatException If the format of the number is not valid. + * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. + * @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding. + */ + public static function ofNullable(BigNumber|int|float|string|null $value): ?static + { + if (is_null($value)) { + return null; + } + + return static::of($value); + } + + /** + * Returns the minimum of the given values. + * + * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible + * to an instance of the class this method is called on. + * + * @throws InvalidArgumentException If no values are given. + * @throws MathException If an argument is not valid. + * + * @pure + */ + final public static function min(BigNumber|int|float|string ...$values): static + { + $min = null; + + foreach ($values as $value) { + $value = static::of($value); + + if ($min === null || $value->isLessThan($min)) { + $min = $value; + } + } + + if ($min === null) { + throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.'); + } + + return $min; + } + + /** + * Returns the maximum of the given values. + * + * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible + * to an instance of the class this method is called on. + * + * @throws InvalidArgumentException If no values are given. + * @throws MathException If an argument is not valid. + * + * @pure + */ + final public static function max(BigNumber|int|float|string ...$values): static + { + $max = null; + + foreach ($values as $value) { + $value = static::of($value); + + if ($max === null || $value->isGreaterThan($max)) { + $max = $value; + } + } + + if ($max === null) { + throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.'); + } + + return $max; + } + + /** + * Returns the sum of the given values. + * + * When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among + * the given values (BigInteger < BigDecimal < BigRational). + * + * When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that + * specific subclass, and returns a result of the same type. + * + * @param BigNumber|int|float|string ...$values The values to add. All values must be convertible to the class on + * which this method is called. + * + * @throws InvalidArgumentException If no values are given. + * @throws MathException If an argument is not valid. + * + * @pure + */ + final public static function sum(BigNumber|int|float|string ...$values): static + { + $first = array_shift($values); + + if ($first === null) { + throw new InvalidArgumentException(__METHOD__ . '() expects at least one value.'); + } + + $sum = static::of($first); + + foreach ($values as $value) { + $sum = self::add($sum, static::of($value)); + } + + assert($sum instanceof static); + + return $sum; + } + + /** + * Checks if this number is equal to the given one. + * + * @pure + */ + final public function isEqualTo(BigNumber|int|float|string $that): bool + { + return $this->compareTo($that) === 0; + } + + /** + * Checks if this number is strictly lower than the given one. + * + * @pure + */ + final public function isLessThan(BigNumber|int|float|string $that): bool + { + return $this->compareTo($that) < 0; + } + + /** + * Checks if this number is lower than or equal to the given one. + * + * @pure + */ + final public function isLessThanOrEqualTo(BigNumber|int|float|string $that): bool + { + return $this->compareTo($that) <= 0; + } + + /** + * Checks if this number is strictly greater than the given one. + * + * @pure + */ + final public function isGreaterThan(BigNumber|int|float|string $that): bool + { + return $this->compareTo($that) > 0; + } + + /** + * Checks if this number is greater than or equal to the given one. + * + * @pure + */ + final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that): bool + { + return $this->compareTo($that) >= 0; + } + + /** + * Checks if this number equals zero. + * + * @pure + */ + final public function isZero(): bool + { + return $this->getSign() === 0; + } + + /** + * Checks if this number is strictly negative. + * + * @pure + */ + final public function isNegative(): bool + { + return $this->getSign() < 0; + } + + /** + * Checks if this number is negative or zero. + * + * @pure + */ + final public function isNegativeOrZero(): bool + { + return $this->getSign() <= 0; + } + + /** + * Checks if this number is strictly positive. + * + * @pure + */ + final public function isPositive(): bool + { + return $this->getSign() > 0; + } + + /** + * Checks if this number is positive or zero. + * + * @pure + */ + final public function isPositiveOrZero(): bool + { + return $this->getSign() >= 0; + } + + /** + * Returns the sign of this number. + * + * Returns -1 if the number is negative, 0 if zero, 1 if positive. + * + * @return -1|0|1 + * + * @pure + */ + abstract public function getSign(): int; + + /** + * Compares this number to the given one. + * + * Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`. + * + * @return -1|0|1 + * + * @throws MathException If the number is not valid. + * + * @pure + */ + abstract public function compareTo(BigNumber|int|float|string $that): int; + + /** + * Converts this number to a BigInteger. + * + * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. + * + * @pure + */ + abstract public function toBigInteger(): BigInteger; + + /** + * Converts this number to a BigDecimal. + * + * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. + * + * @pure + */ + abstract public function toBigDecimal(): BigDecimal; + + /** + * Converts this number to a BigRational. + * + * @pure + */ + abstract public function toBigRational(): BigRational; + + /** + * Converts this number to a BigDecimal with the given scale, using rounding if necessary. + * + * @param int $scale The scale of the resulting `BigDecimal`. + * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. + * + * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. + * This only applies when RoundingMode::UNNECESSARY is used. + * + * @pure + */ + abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal; + + /** + * Returns the exact value of this number as a native integer. + * + * If this number cannot be converted to a native integer without losing precision, an exception is thrown. + * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. + * + * @throws MathException If this number cannot be exactly converted to a native integer. + * + * @pure + */ + abstract public function toInt(): int; + + /** + * Returns an approximation of this number as a floating-point value. + * + * Note that this method can discard information as the precision of a floating-point value + * is inherently limited. + * + * If the number is greater than the largest representable floating point number, positive infinity is returned. + * If the number is less than the smallest representable floating point number, negative infinity is returned. + * + * @pure + */ + abstract public function toFloat(): float; + + #[Override] + final public function jsonSerialize(): string + { + return $this->__toString(); + } + + /** + * Returns a string representation of this number. + * + * The output of this method can be parsed by the `of()` factory method; + * this will yield an object equal to this one, without any information loss. + * + * @pure + */ + abstract public function __toString(): string; + + /** + * Overridden by subclasses to convert a BigNumber to an instance of the subclass. + * + * @throws RoundingNecessaryException If the value cannot be converted. + * + * @pure + */ + abstract protected static function from(BigNumber $number): static; + + /** + * Proxy method to access BigInteger's protected constructor from sibling classes. + * + * @internal + * + * @pure + */ + final protected function newBigInteger(string $value): BigInteger + { + return new BigInteger($value); + } + + /** + * Proxy method to access BigDecimal's protected constructor from sibling classes. + * + * @internal + * + * @pure + */ + final protected function newBigDecimal(string $value, int $scale = 0): BigDecimal + { + return new BigDecimal($value, $scale); + } + + /** + * Proxy method to access BigRational's protected constructor from sibling classes. + * + * @internal + * + * @pure + */ + final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator): BigRational + { + return new BigRational($numerator, $denominator, $checkDenominator); + } + + /** + * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * * @pure */ - private static function _of(BigNumber|int|float|string $value) : BigNumber + private static function _of(BigNumber|int|float|string $value): BigNumber { if ($value instanceof BigNumber) { return $value; } - if (\is_int($value)) { + if (is_int($value)) { return new BigInteger((string) $value); } if (is_float($value)) { - $value = (string) $value; + if (is_nan($value)) { + $value = 'NAN'; + } else { + $value = (string) $value; + } } if (str_contains($value, '/')) { // Rational number - if (\preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { + if (preg_match(self::PARSE_REGEXP_RATIONAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw NumberFormatException::invalidFormat($value); } - $sign = $matches['sign']; - $numerator = $matches['numerator']; + $sign = $matches['sign']; + $numerator = $matches['numerator']; $denominator = $matches['denominator']; - $numerator = self::cleanUp($sign, $numerator); + $numerator = self::cleanUp($sign, $numerator); $denominator = self::cleanUp(null, $denominator); if ($denominator === '0') { @@ -118,11 +507,11 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable return new BigRational( new BigInteger($numerator), new BigInteger($denominator), - false + false, ); } else { // Integer or decimal number - if (\preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { + if (preg_match(self::PARSE_REGEXP_NUMERICAL, $value, $matches, PREG_UNMATCHED_AS_NULL) !== 1) { throw NumberFormatException::invalidFormat($value); } @@ -142,19 +531,36 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable if ($point !== null || $exponent !== null) { $fractional ??= ''; - $exponent = ($exponent !== null) ? (int)$exponent : 0; - if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { + if ($exponent !== null) { + if ($exponent[0] === '-') { + $exponent = ltrim(substr($exponent, 1), '0') ?: '0'; + $exponent = filter_var($exponent, FILTER_VALIDATE_INT); + if ($exponent !== false) { + $exponent = -$exponent; + } + } else { + if ($exponent[0] === '+') { + $exponent = substr($exponent, 1); + } + $exponent = ltrim($exponent, '0') ?: '0'; + $exponent = filter_var($exponent, FILTER_VALIDATE_INT); + } + } else { + $exponent = 0; + } + + if ($exponent === false) { throw new NumberFormatException('Exponent too large.'); } $unscaledValue = self::cleanUp($sign, $integral . $fractional); - $scale = \strlen($fractional) - $exponent; + $scale = strlen($fractional) - $exponent; if ($scale < 0) { if ($unscaledValue !== '0') { - $unscaledValue .= \str_repeat('0', -$scale); + $unscaledValue .= str_repeat('0', -$scale); } $scale = 0; } @@ -169,141 +575,22 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable } /** - * Overridden by subclasses to convert a BigNumber to an instance of the subclass. + * Removes optional leading zeros and applies sign. * - * @throws RoundingNecessaryException If the value cannot be converted. + * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'. + * @param string $number The number, validated as a string of digits. * * @pure */ - abstract protected static function from(BigNumber $number): static; - - /** - * Proxy method to access BigInteger's protected constructor from sibling classes. - * - * @pure - * @internal - */ - final protected function newBigInteger(string $value) : BigInteger + private static function cleanUp(string|null $sign, string $number): string { - return new BigInteger($value); - } + $number = ltrim($number, '0'); - /** - * Proxy method to access BigDecimal's protected constructor from sibling classes. - * - * @pure - * @internal - */ - final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal - { - return new BigDecimal($value, $scale); - } - - /** - * Proxy method to access BigRational's protected constructor from sibling classes. - * - * @pure - * @internal - */ - final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational - { - return new BigRational($numerator, $denominator, $checkDenominator); - } - - /** - * Returns the minimum of the given values. - * - * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible - * to an instance of the class this method is called on. - * - * @throws \InvalidArgumentException If no values are given. - * @throws MathException If an argument is not valid. - * - * @pure - */ - final public static function min(BigNumber|int|float|string ...$values) : static - { - $min = null; - - foreach ($values as $value) { - $value = static::of($value); - - if ($min === null || $value->isLessThan($min)) { - $min = $value; - } + if ($number === '') { + return '0'; } - if ($min === null) { - throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); - } - - return $min; - } - - /** - * Returns the maximum of the given values. - * - * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible - * to an instance of the class this method is called on. - * - * @throws \InvalidArgumentException If no values are given. - * @throws MathException If an argument is not valid. - * - * @pure - */ - final public static function max(BigNumber|int|float|string ...$values) : static - { - $max = null; - - foreach ($values as $value) { - $value = static::of($value); - - if ($max === null || $value->isGreaterThan($max)) { - $max = $value; - } - } - - if ($max === null) { - throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); - } - - return $max; - } - - /** - * Returns the sum of the given values. - * - * When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among - * the given values (BigInteger < BigDecimal < BigRational). - * - * When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that - * specific subclass, and returns a result of the same type. - * - * @param BigNumber|int|float|string ...$values The values to add. All values must be convertible to the class on - * which this method is called. - * - * @throws \InvalidArgumentException If no values are given. - * @throws MathException If an argument is not valid. - * - * @pure - */ - final public static function sum(BigNumber|int|float|string ...$values) : static - { - $first = array_shift($values); - - if ($first === null) { - throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); - } - - $sum = static::of($first); - - foreach ($values as $value) { - $sum = self::add($sum, static::of($value)); - } - - assert($sum instanceof static); - - return $sum; + return $sign === '-' ? '-' . $number : $number; } /** @@ -311,7 +598,7 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable * * @pure */ - private static function add(BigNumber $a, BigNumber $b) : BigNumber + private static function add(BigNumber $a, BigNumber $b): BigNumber { if ($a instanceof BigRational) { return $a->plus($b); @@ -331,226 +618,4 @@ abstract readonly class BigNumber implements \JsonSerializable, \Stringable return $a->plus($b); } - - /** - * Removes optional leading zeros and applies sign. - * - * @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'. - * @param string $number The number, validated as a string of digits. - * - * @pure - */ - private static function cleanUp(string|null $sign, string $number) : string - { - $number = \ltrim($number, '0'); - - if ($number === '') { - return '0'; - } - - return $sign === '-' ? '-' . $number : $number; - } - - /** - * Checks if this number is equal to the given one. - * - * @pure - */ - final public function isEqualTo(BigNumber|int|float|string $that) : bool - { - return $this->compareTo($that) === 0; - } - - /** - * Checks if this number is strictly lower than the given one. - * - * @pure - */ - final public function isLessThan(BigNumber|int|float|string $that) : bool - { - return $this->compareTo($that) < 0; - } - - /** - * Checks if this number is lower than or equal to the given one. - * - * @pure - */ - final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool - { - return $this->compareTo($that) <= 0; - } - - /** - * Checks if this number is strictly greater than the given one. - * - * @pure - */ - final public function isGreaterThan(BigNumber|int|float|string $that) : bool - { - return $this->compareTo($that) > 0; - } - - /** - * Checks if this number is greater than or equal to the given one. - * - * @pure - */ - final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool - { - return $this->compareTo($that) >= 0; - } - - /** - * Checks if this number equals zero. - * - * @pure - */ - final public function isZero() : bool - { - return $this->getSign() === 0; - } - - /** - * Checks if this number is strictly negative. - * - * @pure - */ - final public function isNegative() : bool - { - return $this->getSign() < 0; - } - - /** - * Checks if this number is negative or zero. - * - * @pure - */ - final public function isNegativeOrZero() : bool - { - return $this->getSign() <= 0; - } - - /** - * Checks if this number is strictly positive. - * - * @pure - */ - final public function isPositive() : bool - { - return $this->getSign() > 0; - } - - /** - * Checks if this number is positive or zero. - * - * @pure - */ - final public function isPositiveOrZero() : bool - { - return $this->getSign() >= 0; - } - - /** - * Returns the sign of this number. - * - * Returns -1 if the number is negative, 0 if zero, 1 if positive. - * - * @return -1|0|1 - * - * @pure - */ - abstract public function getSign() : int; - - /** - * Compares this number to the given one. - * - * Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`. - * - * @return -1|0|1 - * - * @throws MathException If the number is not valid. - * - * @pure - */ - abstract public function compareTo(BigNumber|int|float|string $that) : int; - - /** - * Converts this number to a BigInteger. - * - * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. - * - * @pure - */ - abstract public function toBigInteger() : BigInteger; - - /** - * Converts this number to a BigDecimal. - * - * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. - * - * @pure - */ - abstract public function toBigDecimal() : BigDecimal; - - /** - * Converts this number to a BigRational. - * - * @pure - */ - abstract public function toBigRational() : BigRational; - - /** - * Converts this number to a BigDecimal with the given scale, using rounding if necessary. - * - * @param int $scale The scale of the resulting `BigDecimal`. - * @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY. - * - * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. - * This only applies when RoundingMode::UNNECESSARY is used. - * - * @pure - */ - abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; - - /** - * Returns the exact value of this number as a native integer. - * - * If this number cannot be converted to a native integer without losing precision, an exception is thrown. - * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. - * - * @throws MathException If this number cannot be exactly converted to a native integer. - * - * @pure - */ - abstract public function toInt() : int; - - /** - * Returns an approximation of this number as a floating-point value. - * - * Note that this method can discard information as the precision of a floating-point value - * is inherently limited. - * - * If the number is greater than the largest representable floating point number, positive infinity is returned. - * If the number is less than the smallest representable floating point number, negative infinity is returned. - * - * @pure - */ - abstract public function toFloat() : float; - - /** - * Returns a string representation of this number. - * - * The output of this method can be parsed by the `of()` factory method; - * this will yield an object equal to this one, without any information loss. - * - * @pure - */ - abstract public function __toString() : string; - - #[Override] - final public function jsonSerialize() : string - { - return $this->__toString(); - } } diff --git a/vendor/brick/math/src/BigRational.php b/vendor/brick/math/src/BigRational.php index 56045e537..09b091682 100644 --- a/vendor/brick/math/src/BigRational.php +++ b/vendor/brick/math/src/BigRational.php @@ -8,6 +8,8 @@ use Brick\Math\Exception\DivisionByZeroException; use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; use Brick\Math\Exception\RoundingNecessaryException; +use InvalidArgumentException; +use LogicException; use Override; /** @@ -46,21 +48,15 @@ final readonly class BigRational extends BigNumber } if ($denominator->isNegative()) { - $numerator = $numerator->negated(); + $numerator = $numerator->negated(); $denominator = $denominator->negated(); } } - $this->numerator = $numerator; + $this->numerator = $numerator; $this->denominator = $denominator; } - #[Override] - protected static function from(BigNumber $number): static - { - return $number->toBigRational(); - } - /** * Creates a BigRational out of a numerator and a denominator. * @@ -79,8 +75,8 @@ final readonly class BigRational extends BigNumber public static function nd( BigNumber|int|float|string $numerator, BigNumber|int|float|string $denominator, - ) : BigRational { - $numerator = BigInteger::of($numerator); + ): BigRational { + $numerator = BigInteger::of($numerator); $denominator = BigInteger::of($denominator); return new BigRational($numerator, $denominator, true); @@ -91,7 +87,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public static function zero() : BigRational + public static function zero(): BigRational { /** @var BigRational|null $zero */ static $zero; @@ -108,7 +104,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public static function one() : BigRational + public static function one(): BigRational { /** @var BigRational|null $one */ static $one; @@ -125,7 +121,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public static function ten() : BigRational + public static function ten(): BigRational { /** @var BigRational|null $ten */ static $ten; @@ -140,7 +136,7 @@ final readonly class BigRational extends BigNumber /** * @pure */ - public function getNumerator() : BigInteger + public function getNumerator(): BigInteger { return $this->numerator; } @@ -148,7 +144,7 @@ final readonly class BigRational extends BigNumber /** * @pure */ - public function getDenominator() : BigInteger + public function getDenominator(): BigInteger { return $this->denominator; } @@ -158,7 +154,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function quotient() : BigInteger + public function quotient(): BigInteger { return $this->numerator->quotient($this->denominator); } @@ -168,7 +164,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function remainder() : BigInteger + public function remainder(): BigInteger { return $this->numerator->remainder($this->denominator); } @@ -180,7 +176,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function quotientAndRemainder() : array + public function quotientAndRemainder(): array { return $this->numerator->quotientAndRemainder($this->denominator); } @@ -194,12 +190,12 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function plus(BigNumber|int|float|string $that) : BigRational + public function plus(BigNumber|int|float|string $that): BigRational { $that = BigRational::of($that); - $numerator = $this->numerator->multipliedBy($that->denominator); - $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); + $numerator = $this->numerator->multipliedBy($that->denominator); + $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); @@ -214,12 +210,12 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function minus(BigNumber|int|float|string $that) : BigRational + public function minus(BigNumber|int|float|string $that): BigRational { $that = BigRational::of($that); - $numerator = $this->numerator->multipliedBy($that->denominator); - $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); + $numerator = $this->numerator->multipliedBy($that->denominator); + $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); @@ -234,11 +230,11 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function multipliedBy(BigNumber|int|float|string $that) : BigRational + public function multipliedBy(BigNumber|int|float|string $that): BigRational { $that = BigRational::of($that); - $numerator = $this->numerator->multipliedBy($that->numerator); + $numerator = $this->numerator->multipliedBy($that->numerator); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); @@ -253,11 +249,11 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function dividedBy(BigNumber|int|float|string $that) : BigRational + public function dividedBy(BigNumber|int|float|string $that): BigRational { $that = BigRational::of($that); - $numerator = $this->numerator->multipliedBy($that->denominator); + $numerator = $this->numerator->multipliedBy($that->denominator); $denominator = $this->denominator->multipliedBy($that->numerator); return new BigRational($numerator, $denominator, true); @@ -266,11 +262,11 @@ final readonly class BigRational extends BigNumber /** * Returns this number exponentiated to the given value. * - * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. + * @throws InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. * * @pure */ - public function power(int $exponent) : BigRational + public function power(int $exponent): BigRational { if ($exponent === 0) { $one = BigInteger::one(); @@ -285,7 +281,7 @@ final readonly class BigRational extends BigNumber return new BigRational( $this->numerator->power($exponent), $this->denominator->power($exponent), - false + false, ); } @@ -298,7 +294,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function reciprocal() : BigRational + public function reciprocal(): BigRational { return new BigRational($this->denominator, $this->numerator, true); } @@ -308,7 +304,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function abs() : BigRational + public function abs(): BigRational { return new BigRational($this->numerator->abs(), $this->denominator, false); } @@ -318,7 +314,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function negated() : BigRational + public function negated(): BigRational { return new BigRational($this->numerator->negated(), $this->denominator, false); } @@ -328,7 +324,7 @@ final readonly class BigRational extends BigNumber * * @pure */ - public function simplified() : BigRational + public function simplified(): BigRational { $gcd = $this->numerator->gcd($this->denominator); @@ -339,19 +335,19 @@ final readonly class BigRational extends BigNumber } #[Override] - public function compareTo(BigNumber|int|float|string $that) : int + public function compareTo(BigNumber|int|float|string $that): int { return $this->minus($that)->getSign(); } #[Override] - public function getSign() : int + public function getSign(): int { return $this->numerator->getSign(); } #[Override] - public function toBigInteger() : BigInteger + public function toBigInteger(): BigInteger { $simplified = $this->simplified(); @@ -363,40 +359,41 @@ final readonly class BigRational extends BigNumber } #[Override] - public function toBigDecimal() : BigDecimal + public function toBigDecimal(): BigDecimal { return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); } #[Override] - public function toBigRational() : BigRational + public function toBigRational(): BigRational { return $this; } #[Override] - public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal + public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal { return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); } #[Override] - public function toInt() : int + public function toInt(): int { return $this->toBigInteger()->toInt(); } #[Override] - public function toFloat() : float + public function toFloat(): float { $simplified = $this->simplified(); + return $simplified->numerator->toFloat() / $simplified->denominator->toFloat(); } #[Override] - public function __toString() : string + public function __toString(): string { - $numerator = (string) $this->numerator; + $numerator = (string) $this->numerator; $denominator = (string) $this->denominator; if ($denominator === '1') { @@ -425,17 +422,23 @@ final readonly class BigRational extends BigNumber * * @param array{numerator: BigInteger, denominator: BigInteger} $data * - * @throws \LogicException + * @throws LogicException */ public function __unserialize(array $data): void { /** @phpstan-ignore isset.initializedProperty */ if (isset($this->numerator)) { - throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); + throw new LogicException('__unserialize() is an internal function, it must not be called directly.'); } /** @phpstan-ignore deadCode.unreachable */ $this->numerator = $data['numerator']; $this->denominator = $data['denominator']; } + + #[Override] + protected static function from(BigNumber $number): static + { + return $number->toBigRational(); + } } diff --git a/vendor/brick/math/src/Exception/DivisionByZeroException.php b/vendor/brick/math/src/Exception/DivisionByZeroException.php index 2daaf5ed8..0fcd33be4 100644 --- a/vendor/brick/math/src/Exception/DivisionByZeroException.php +++ b/vendor/brick/math/src/Exception/DivisionByZeroException.php @@ -12,7 +12,7 @@ final class DivisionByZeroException extends MathException /** * @pure */ - public static function divisionByZero() : DivisionByZeroException + public static function divisionByZero(): DivisionByZeroException { return new self('Division by zero.'); } @@ -20,7 +20,7 @@ final class DivisionByZeroException extends MathException /** * @pure */ - public static function modulusMustNotBeZero() : DivisionByZeroException + public static function modulusMustNotBeZero(): DivisionByZeroException { return new self('The modulus must not be zero.'); } @@ -28,7 +28,7 @@ final class DivisionByZeroException extends MathException /** * @pure */ - public static function denominatorMustNotBeZero() : DivisionByZeroException + public static function denominatorMustNotBeZero(): DivisionByZeroException { return new self('The denominator of a rational number cannot be zero.'); } diff --git a/vendor/brick/math/src/Exception/IntegerOverflowException.php b/vendor/brick/math/src/Exception/IntegerOverflowException.php index 56f3cf84c..496fd0c53 100644 --- a/vendor/brick/math/src/Exception/IntegerOverflowException.php +++ b/vendor/brick/math/src/Exception/IntegerOverflowException.php @@ -6,6 +6,11 @@ namespace Brick\Math\Exception; use Brick\Math\BigInteger; +use function sprintf; + +use const PHP_INT_MAX; +use const PHP_INT_MIN; + /** * Exception thrown when an integer overflow occurs. */ @@ -14,10 +19,10 @@ final class IntegerOverflowException extends MathException /** * @pure */ - public static function toIntOverflow(BigInteger $value) : IntegerOverflowException + public static function toIntOverflow(BigInteger $value): IntegerOverflowException { $message = '%s is out of range %d to %d and cannot be represented as an integer.'; - return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX)); + return new self(sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX)); } } diff --git a/vendor/brick/math/src/Exception/MathException.php b/vendor/brick/math/src/Exception/MathException.php index 8facd9c62..5b83acfc1 100644 --- a/vendor/brick/math/src/Exception/MathException.php +++ b/vendor/brick/math/src/Exception/MathException.php @@ -4,9 +4,11 @@ declare(strict_types=1); namespace Brick\Math\Exception; +use RuntimeException; + /** * Base class for all math exceptions. */ -class MathException extends \RuntimeException +class MathException extends RuntimeException { } diff --git a/vendor/brick/math/src/Exception/NumberFormatException.php b/vendor/brick/math/src/Exception/NumberFormatException.php index 0dcda3483..072e12006 100644 --- a/vendor/brick/math/src/Exception/NumberFormatException.php +++ b/vendor/brick/math/src/Exception/NumberFormatException.php @@ -4,6 +4,11 @@ declare(strict_types=1); namespace Brick\Math\Exception; +use function dechex; +use function ord; +use function sprintf; +use function strtoupper; + /** * Exception thrown when attempting to create a number from a string with an invalid format. */ @@ -12,9 +17,9 @@ final class NumberFormatException extends MathException /** * @pure */ - public static function invalidFormat(string $value) : self + public static function invalidFormat(string $value): self { - return new self(\sprintf( + return new self(sprintf( 'The given value "%s" does not represent a valid number.', $value, )); @@ -25,12 +30,12 @@ final class NumberFormatException extends MathException * * @pure */ - public static function charNotInAlphabet(string $char) : self + public static function charNotInAlphabet(string $char): self { - $ord = \ord($char); + $ord = ord($char); if ($ord < 32 || $ord > 126) { - $char = \strtoupper(\dechex($ord)); + $char = strtoupper(dechex($ord)); if ($ord < 10) { $char = '0' . $char; @@ -39,6 +44,6 @@ final class NumberFormatException extends MathException $char = '"' . $char . '"'; } - return new self(\sprintf('Char %s is not a valid character in the given alphabet.', $char)); + return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char)); } } diff --git a/vendor/brick/math/src/Exception/RoundingNecessaryException.php b/vendor/brick/math/src/Exception/RoundingNecessaryException.php index 034a04069..e5157a640 100644 --- a/vendor/brick/math/src/Exception/RoundingNecessaryException.php +++ b/vendor/brick/math/src/Exception/RoundingNecessaryException.php @@ -12,7 +12,7 @@ final class RoundingNecessaryException extends MathException /** * @pure */ - public static function roundingNecessary() : RoundingNecessaryException + public static function roundingNecessary(): RoundingNecessaryException { return new self('Rounding is necessary to represent the result of the operation at this scale.'); } diff --git a/vendor/brick/math/src/Internal/Calculator.php b/vendor/brick/math/src/Internal/Calculator.php index a5e000a42..8c3a87f14 100644 --- a/vendor/brick/math/src/Internal/Calculator.php +++ b/vendor/brick/math/src/Internal/Calculator.php @@ -7,6 +7,16 @@ namespace Brick\Math\Internal; use Brick\Math\Exception\RoundingNecessaryException; use Brick\Math\RoundingMode; +use function chr; +use function ltrim; +use function ord; +use function str_repeat; +use function strlen; +use function strpos; +use function strrev; +use function strtolower; +use function substr; + /** * Performs basic operations on arbitrary size integers. * @@ -30,32 +40,14 @@ abstract readonly class Calculator */ public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'; - /** - * Extracts the sign & digits of the operands. - * - * @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits. - * - * @pure - */ - final protected function init(string $a, string $b) : array - { - return [ - $aNeg = ($a[0] === '-'), - $bNeg = ($b[0] === '-'), - - $aNeg ? \substr($a, 1) : $a, - $bNeg ? \substr($b, 1) : $b, - ]; - } - /** * Returns the absolute value of a number. * * @pure */ - final public function abs(string $n) : string + final public function abs(string $n): string { - return ($n[0] === '-') ? \substr($n, 1) : $n; + return ($n[0] === '-') ? substr($n, 1) : $n; } /** @@ -63,14 +55,14 @@ abstract readonly class Calculator * * @pure */ - final public function neg(string $n) : string + final public function neg(string $n): string { if ($n === '0') { return '0'; } if ($n[0] === '-') { - return \substr($n, 1); + return substr($n, 1); } return '-' . $n; @@ -85,7 +77,7 @@ abstract readonly class Calculator * * @pure */ - final public function cmp(string $a, string $b) : int + final public function cmp(string $a, string $b): int { [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); @@ -97,8 +89,8 @@ abstract readonly class Calculator return 1; } - $aLen = \strlen($aDig); - $bLen = \strlen($bDig); + $aLen = strlen($aDig); + $bLen = strlen($bDig); if ($aLen < $bLen) { $result = -1; @@ -116,21 +108,21 @@ abstract readonly class Calculator * * @pure */ - abstract public function add(string $a, string $b) : string; + abstract public function add(string $a, string $b): string; /** * Subtracts two numbers. * * @pure */ - abstract public function sub(string $a, string $b) : string; + abstract public function sub(string $a, string $b): string; /** * Multiplies two numbers. * * @pure */ - abstract public function mul(string $a, string $b) : string; + abstract public function mul(string $a, string $b): string; /** * Returns the quotient of the division of two numbers. @@ -142,7 +134,7 @@ abstract readonly class Calculator * * @pure */ - abstract public function divQ(string $a, string $b) : string; + abstract public function divQ(string $a, string $b): string; /** * Returns the remainder of the division of two numbers. @@ -154,7 +146,7 @@ abstract readonly class Calculator * * @pure */ - abstract public function divR(string $a, string $b) : string; + abstract public function divR(string $a, string $b): string; /** * Returns the quotient and remainder of the division of two numbers. @@ -166,7 +158,7 @@ abstract readonly class Calculator * * @pure */ - abstract public function divQR(string $a, string $b) : array; + abstract public function divQR(string $a, string $b): array; /** * Exponentiates a number. @@ -178,14 +170,14 @@ abstract readonly class Calculator * * @pure */ - abstract public function pow(string $a, int $e) : string; + abstract public function pow(string $a, int $e): string; /** * @param string $b The modulus; must not be zero. * * @pure */ - public function mod(string $a, string $b) : string + public function mod(string $a, string $b): string { return $this->divR($this->add($this->divR($a, $b), $b), $b); } @@ -201,7 +193,7 @@ abstract readonly class Calculator * * @pure */ - public function modInverse(string $x, string $m) : ?string + public function modInverse(string $x, string $m): ?string { if ($m === '1') { return '0'; @@ -231,7 +223,7 @@ abstract readonly class Calculator * * @pure */ - abstract public function modPow(string $base, string $exp, string $mod) : string; + abstract public function modPow(string $base, string $exp, string $mod): string; /** * Returns the greatest common divisor of the two numbers. @@ -243,7 +235,7 @@ abstract readonly class Calculator * * @pure */ - public function gcd(string $a, string $b) : string + public function gcd(string $a, string $b): string { if ($a === '0') { return $this->abs($b); @@ -256,25 +248,6 @@ abstract readonly class Calculator return $this->gcd($b, $this->divR($a, $b)); } - /** - * @return array{string, string, string} GCD, X, Y - * - * @pure - */ - private function gcdExtended(string $a, string $b) : array - { - if ($a === '0') { - return [$b, '0', '1']; - } - - [$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a); - - $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); - $y = $x1; - - return [$gcd, $x, $y]; - } - /** * Returns the square root of the given number, rounded down. * @@ -283,7 +256,7 @@ abstract readonly class Calculator * * @pure */ - abstract public function sqrt(string $n) : string; + abstract public function sqrt(string $n): string; /** * Converts a number from an arbitrary base. @@ -298,9 +271,9 @@ abstract readonly class Calculator * * @pure */ - public function fromBase(string $number, int $base) : string + public function fromBase(string $number, int $base): string { - return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); + return $this->fromArbitraryBase(strtolower($number), self::ALPHABET, $base); } /** @@ -316,12 +289,12 @@ abstract readonly class Calculator * * @pure */ - public function toBase(string $number, int $base) : string + public function toBase(string $number, int $base): string { $negative = ($number[0] === '-'); if ($negative) { - $number = \substr($number, 1); + $number = substr($number, 1); } $number = $this->toArbitraryBase($number, self::ALPHABET, $base); @@ -345,10 +318,10 @@ abstract readonly class Calculator * * @pure */ - final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string + final public function fromArbitraryBase(string $number, string $alphabet, int $base): string { // remove leading "zeros" - $number = \ltrim($number, $alphabet[0]); + $number = ltrim($number, $alphabet[0]); if ($number === '') { return '0'; @@ -364,13 +337,13 @@ abstract readonly class Calculator $base = (string) $base; - for ($i = \strlen($number) - 1; $i >= 0; $i--) { - $index = \strpos($alphabet, $number[$i]); + for ($i = strlen($number) - 1; $i >= 0; $i--) { + $index = strpos($alphabet, $number[$i]); if ($index !== 0) { - $result = $this->add($result, ($index === 1) - ? $power - : $this->mul($power, (string) $index) + $result = $this->add( + $result, + ($index === 1) ? $power : $this->mul($power, (string) $index), ); } @@ -393,7 +366,7 @@ abstract readonly class Calculator * * @pure */ - final public function toArbitraryBase(string $number, string $alphabet, int $base) : string + final public function toArbitraryBase(string $number, string $alphabet, int $base): string { if ($number === '0') { return $alphabet[0]; @@ -409,7 +382,7 @@ abstract readonly class Calculator $result .= $alphabet[$remainder]; } - return \strrev($result); + return strrev($result); } /** @@ -425,14 +398,14 @@ abstract readonly class Calculator * * @pure */ - final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string + final public function divRound(string $a, string $b, RoundingMode $roundingMode): string { [$quotient, $remainder] = $this->divQR($a, $b); $hasDiscardedFraction = ($remainder !== '0'); $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); - $discardedFractionSign = function() use ($remainder, $b) : int { + $discardedFractionSign = function () use ($remainder, $b): int { $r = $this->abs($this->mul($remainder, '2')); $b = $this->abs($b); @@ -446,10 +419,12 @@ abstract readonly class Calculator if ($hasDiscardedFraction) { throw RoundingNecessaryException::roundingNecessary(); } + break; case RoundingMode::UP: $increment = $hasDiscardedFraction; + break; case RoundingMode::DOWN: @@ -457,32 +432,39 @@ abstract readonly class Calculator case RoundingMode::CEILING: $increment = $hasDiscardedFraction && $isPositiveOrZero; + break; case RoundingMode::FLOOR: $increment = $hasDiscardedFraction && ! $isPositiveOrZero; + break; case RoundingMode::HALF_UP: $increment = $discardedFractionSign() >= 0; + break; case RoundingMode::HALF_DOWN: $increment = $discardedFractionSign() > 0; + break; case RoundingMode::HALF_CEILING: $increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; + break; case RoundingMode::HALF_FLOOR: $increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; + break; case RoundingMode::HALF_EVEN: $lastDigit = (int) $quotient[-1]; $lastDigitIsEven = ($lastDigit % 2 === 0); $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; + break; } @@ -501,7 +483,7 @@ abstract readonly class Calculator * * @pure */ - public function and(string $a, string $b) : string + public function and(string $a, string $b): string { return $this->bitwise('and', $a, $b); } @@ -514,7 +496,7 @@ abstract readonly class Calculator * * @pure */ - public function or(string $a, string $b) : string + public function or(string $a, string $b): string { return $this->bitwise('or', $a, $b); } @@ -527,11 +509,48 @@ abstract readonly class Calculator * * @pure */ - public function xor(string $a, string $b) : string + public function xor(string $a, string $b): string { return $this->bitwise('xor', $a, $b); } + /** + * Extracts the sign & digits of the operands. + * + * @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits. + * + * @pure + */ + final protected function init(string $a, string $b): array + { + return [ + $aNeg = ($a[0] === '-'), + $bNeg = ($b[0] === '-'), + + $aNeg ? substr($a, 1) : $a, + $bNeg ? substr($b, 1) : $b, + ]; + } + + /** + * @return array{string, string, string} GCD, X, Y + * + * @pure + */ + private function gcdExtended(string $a, string $b): array + { + if ($a === '0') { + return [$b, '0', '1']; + } + + [$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a); + + $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); + $y = $x1; + + return [$gcd, $x, $y]; + } + /** * Performs a bitwise operation on a decimal number. * @@ -541,20 +560,20 @@ abstract readonly class Calculator * * @pure */ - private function bitwise(string $operator, string $a, string $b) : string + private function bitwise(string $operator, string $a, string $b): string { [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $aBin = $this->toBinary($aDig); $bBin = $this->toBinary($bDig); - $aLen = \strlen($aBin); - $bLen = \strlen($bBin); + $aLen = strlen($aBin); + $bLen = strlen($bBin); if ($aLen > $bLen) { - $bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; + $bBin = str_repeat("\x00", $aLen - $bLen) . $bBin; } elseif ($bLen > $aLen) { - $aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; + $aBin = str_repeat("\x00", $bLen - $aLen) . $aBin; } if ($aNeg) { @@ -590,17 +609,18 @@ abstract readonly class Calculator * * @pure */ - private function twosComplement(string $number) : string + private function twosComplement(string $number): string { - $xor = \str_repeat("\xff", \strlen($number)); + $xor = str_repeat("\xff", strlen($number)); $number ^= $xor; - for ($i = \strlen($number) - 1; $i >= 0; $i--) { - $byte = \ord($number[$i]); + for ($i = strlen($number) - 1; $i >= 0; $i--) { + $byte = ord($number[$i]); if (++$byte !== 256) { - $number[$i] = \chr($byte); + $number[$i] = chr($byte); + break; } @@ -621,16 +641,16 @@ abstract readonly class Calculator * * @pure */ - private function toBinary(string $number) : string + private function toBinary(string $number): string { $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, '256'); - $result .= \chr((int) $remainder); + $result .= chr((int) $remainder); } - return \strrev($result); + return strrev($result); } /** @@ -640,18 +660,18 @@ abstract readonly class Calculator * * @pure */ - private function toDecimal(string $bytes) : string + private function toDecimal(string $bytes): string { $result = '0'; $power = '1'; - for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { - $index = \ord($bytes[$i]); + for ($i = strlen($bytes) - 1; $i >= 0; $i--) { + $index = ord($bytes[$i]); if ($index !== 0) { - $result = $this->add($result, ($index === 1) - ? $power - : $this->mul($power, (string) $index) + $result = $this->add( + $result, + ($index === 1) ? $power : $this->mul($power, (string) $index), ); } diff --git a/vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php b/vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php index 5ba961ed3..4115f8b40 100644 --- a/vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php +++ b/vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php @@ -7,6 +7,15 @@ namespace Brick\Math\Internal\Calculator; use Brick\Math\Internal\Calculator; use Override; +use function bcadd; +use function bcdiv; +use function bcmod; +use function bcmul; +use function bcpow; +use function bcpowmod; +use function bcsqrt; +use function bcsub; + /** * Calculator implementation built around the bcmath library. * @@ -15,59 +24,59 @@ use Override; final readonly class BcMathCalculator extends Calculator { #[Override] - public function add(string $a, string $b) : string + public function add(string $a, string $b): string { - return \bcadd($a, $b, 0); + return bcadd($a, $b, 0); } #[Override] - public function sub(string $a, string $b) : string + public function sub(string $a, string $b): string { - return \bcsub($a, $b, 0); + return bcsub($a, $b, 0); } #[Override] - public function mul(string $a, string $b) : string + public function mul(string $a, string $b): string { - return \bcmul($a, $b, 0); + return bcmul($a, $b, 0); } #[Override] - public function divQ(string $a, string $b) : string + public function divQ(string $a, string $b): string { - return \bcdiv($a, $b, 0); + return bcdiv($a, $b, 0); } #[Override] - public function divR(string $a, string $b) : string + public function divR(string $a, string $b): string { - return \bcmod($a, $b, 0); + return bcmod($a, $b, 0); } #[Override] - public function divQR(string $a, string $b) : array + public function divQR(string $a, string $b): array { - $q = \bcdiv($a, $b, 0); - $r = \bcmod($a, $b, 0); + $q = bcdiv($a, $b, 0); + $r = bcmod($a, $b, 0); return [$q, $r]; } #[Override] - public function pow(string $a, int $e) : string + public function pow(string $a, int $e): string { - return \bcpow($a, (string) $e, 0); + return bcpow($a, (string) $e, 0); } #[Override] - public function modPow(string $base, string $exp, string $mod) : string + public function modPow(string $base, string $exp, string $mod): string { - return \bcpowmod($base, $exp, $mod, 0); + return bcpowmod($base, $exp, $mod, 0); } #[Override] - public function sqrt(string $n) : string + public function sqrt(string $n): string { - return \bcsqrt($n, 0); + return bcsqrt($n, 0); } } diff --git a/vendor/brick/math/src/Internal/Calculator/GmpCalculator.php b/vendor/brick/math/src/Internal/Calculator/GmpCalculator.php index c0f9bd585..7a29266d5 100644 --- a/vendor/brick/math/src/Internal/Calculator/GmpCalculator.php +++ b/vendor/brick/math/src/Internal/Calculator/GmpCalculator.php @@ -8,6 +8,23 @@ use Brick\Math\Internal\Calculator; use GMP; use Override; +use function gmp_add; +use function gmp_and; +use function gmp_div_q; +use function gmp_div_qr; +use function gmp_div_r; +use function gmp_gcd; +use function gmp_init; +use function gmp_invert; +use function gmp_mul; +use function gmp_or; +use function gmp_pow; +use function gmp_powm; +use function gmp_sqrt; +use function gmp_strval; +use function gmp_sub; +use function gmp_xor; + /** * Calculator implementation built around the GMP library. * @@ -16,113 +33,113 @@ use Override; final readonly class GmpCalculator extends Calculator { #[Override] - public function add(string $a, string $b) : string + public function add(string $a, string $b): string { - return \gmp_strval(\gmp_add($a, $b)); + return gmp_strval(gmp_add($a, $b)); } #[Override] - public function sub(string $a, string $b) : string + public function sub(string $a, string $b): string { - return \gmp_strval(\gmp_sub($a, $b)); + return gmp_strval(gmp_sub($a, $b)); } #[Override] - public function mul(string $a, string $b) : string + public function mul(string $a, string $b): string { - return \gmp_strval(\gmp_mul($a, $b)); + return gmp_strval(gmp_mul($a, $b)); } #[Override] - public function divQ(string $a, string $b) : string + public function divQ(string $a, string $b): string { - return \gmp_strval(\gmp_div_q($a, $b)); + return gmp_strval(gmp_div_q($a, $b)); } #[Override] - public function divR(string $a, string $b) : string + public function divR(string $a, string $b): string { - return \gmp_strval(\gmp_div_r($a, $b)); + return gmp_strval(gmp_div_r($a, $b)); } #[Override] - public function divQR(string $a, string $b) : array + public function divQR(string $a, string $b): array { - [$q, $r] = \gmp_div_qr($a, $b); + [$q, $r] = gmp_div_qr($a, $b); /** * @var GMP $q * @var GMP $r */ return [ - \gmp_strval($q), - \gmp_strval($r) + gmp_strval($q), + gmp_strval($r), ]; } #[Override] - public function pow(string $a, int $e) : string + public function pow(string $a, int $e): string { - return \gmp_strval(\gmp_pow($a, $e)); + return gmp_strval(gmp_pow($a, $e)); } #[Override] - public function modInverse(string $x, string $m) : ?string + public function modInverse(string $x, string $m): ?string { - $result = \gmp_invert($x, $m); + $result = gmp_invert($x, $m); if ($result === false) { return null; } - return \gmp_strval($result); + return gmp_strval($result); } #[Override] - public function modPow(string $base, string $exp, string $mod) : string + public function modPow(string $base, string $exp, string $mod): string { - return \gmp_strval(\gmp_powm($base, $exp, $mod)); + return gmp_strval(gmp_powm($base, $exp, $mod)); } #[Override] - public function gcd(string $a, string $b) : string + public function gcd(string $a, string $b): string { - return \gmp_strval(\gmp_gcd($a, $b)); + return gmp_strval(gmp_gcd($a, $b)); } #[Override] - public function fromBase(string $number, int $base) : string + public function fromBase(string $number, int $base): string { - return \gmp_strval(\gmp_init($number, $base)); + return gmp_strval(gmp_init($number, $base)); } #[Override] - public function toBase(string $number, int $base) : string + public function toBase(string $number, int $base): string { - return \gmp_strval($number, $base); + return gmp_strval($number, $base); } #[Override] - public function and(string $a, string $b) : string + public function and(string $a, string $b): string { - return \gmp_strval(\gmp_and($a, $b)); + return gmp_strval(gmp_and($a, $b)); } #[Override] - public function or(string $a, string $b) : string + public function or(string $a, string $b): string { - return \gmp_strval(\gmp_or($a, $b)); + return gmp_strval(gmp_or($a, $b)); } #[Override] - public function xor(string $a, string $b) : string + public function xor(string $a, string $b): string { - return \gmp_strval(\gmp_xor($a, $b)); + return gmp_strval(gmp_xor($a, $b)); } #[Override] - public function sqrt(string $n) : string + public function sqrt(string $n): string { - return \gmp_strval(\gmp_sqrt($n)); + return gmp_strval(gmp_sqrt($n)); } } diff --git a/vendor/brick/math/src/Internal/Calculator/NativeCalculator.php b/vendor/brick/math/src/Internal/Calculator/NativeCalculator.php index ee476c9f7..18eb013bb 100644 --- a/vendor/brick/math/src/Internal/Calculator/NativeCalculator.php +++ b/vendor/brick/math/src/Internal/Calculator/NativeCalculator.php @@ -7,6 +7,20 @@ namespace Brick\Math\Internal\Calculator; use Brick\Math\Internal\Calculator; use Override; +use function assert; +use function in_array; +use function intdiv; +use function is_int; +use function ltrim; +use function str_pad; +use function str_repeat; +use function strcmp; +use function strlen; +use function substr; + +use const PHP_INT_SIZE; +use const STR_PAD_LEFT; + /** * Calculator implementation using only native PHP code. * @@ -26,6 +40,7 @@ final readonly class NativeCalculator extends Calculator /** * @pure + * * @codeCoverageIgnore */ public function __construct() @@ -37,7 +52,7 @@ final readonly class NativeCalculator extends Calculator } #[Override] - public function add(string $a, string $b) : string + public function add(string $a, string $b): string { /** * @var numeric-string $a @@ -69,13 +84,13 @@ final readonly class NativeCalculator extends Calculator } #[Override] - public function sub(string $a, string $b) : string + public function sub(string $a, string $b): string { return $this->add($a, $this->neg($b)); } #[Override] - public function mul(string $a, string $b) : string + public function mul(string $a, string $b): string { /** * @var numeric-string $a @@ -119,7 +134,7 @@ final readonly class NativeCalculator extends Calculator } #[Override] - public function divQ(string $a, string $b) : string + public function divQ(string $a, string $b): string { return $this->divQR($a, $b)[0]; } @@ -131,7 +146,7 @@ final readonly class NativeCalculator extends Calculator } #[Override] - public function divQR(string $a, string $b) : array + public function divQR(string $a, string $b): array { if ($a === '0') { return ['0', '0']; @@ -164,7 +179,7 @@ final readonly class NativeCalculator extends Calculator return [ (string) $q, - (string) $r + (string) $r, ]; } } @@ -185,7 +200,7 @@ final readonly class NativeCalculator extends Calculator } #[Override] - public function pow(string $a, int $e) : string + public function pow(string $a, int $e): string { if ($e === 0) { return '1'; @@ -210,10 +225,10 @@ final readonly class NativeCalculator extends Calculator } /** - * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ + * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/. */ #[Override] - public function modPow(string $base, string $exp, string $mod) : string + public function modPow(string $base, string $exp, string $mod): string { // special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0) if ($base === '0' && $exp === '0' && $mod === '1') { @@ -245,21 +260,21 @@ final readonly class NativeCalculator extends Calculator } /** - * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html + * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html. */ #[Override] - public function sqrt(string $n) : string + public function sqrt(string $n): string { if ($n === '0') { return '0'; } // initial approximation - $x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1); + $x = str_repeat('9', intdiv(strlen($n), 2) ?: 1); $decreased = false; - for (;;) { + for (; ;) { $nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2'); if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) { @@ -278,14 +293,14 @@ final readonly class NativeCalculator extends Calculator * * @pure */ - private function doAdd(string $a, string $b) : string + private function doAdd(string $a, string $b): string { [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; - for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { + for ($i = $length - $this->maxDigits; ; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { @@ -294,20 +309,20 @@ final readonly class NativeCalculator extends Calculator } /** @var numeric-string $blockA */ - $blockA = \substr($a, $i, $blockLength); + $blockA = substr($a, $i, $blockLength); /** @var numeric-string $blockB */ - $blockB = \substr($b, $i, $blockLength); + $blockB = substr($b, $i, $blockLength); $sum = (string) ($blockA + $blockB + $carry); - $sumLength = \strlen($sum); + $sumLength = strlen($sum); if ($sumLength > $blockLength) { - $sum = \substr($sum, 1); + $sum = substr($sum, 1); $carry = 1; } else { if ($sumLength < $blockLength) { - $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; + $sum = str_repeat('0', $blockLength - $sumLength) . $sum; } $carry = 0; } @@ -331,7 +346,7 @@ final readonly class NativeCalculator extends Calculator * * @pure */ - private function doSub(string $a, string $b) : string + private function doSub(string $a, string $b): string { if ($a === $b) { return '0'; @@ -355,7 +370,7 @@ final readonly class NativeCalculator extends Calculator $complement = 10 ** $this->maxDigits; - for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { + for ($i = $length - $this->maxDigits; ; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { @@ -364,10 +379,10 @@ final readonly class NativeCalculator extends Calculator } /** @var numeric-string $blockA */ - $blockA = \substr($a, $i, $blockLength); + $blockA = substr($a, $i, $blockLength); /** @var numeric-string $blockB */ - $blockB = \substr($b, $i, $blockLength); + $blockB = substr($b, $i, $blockLength); $sum = $blockA - $blockB - $carry; @@ -379,10 +394,10 @@ final readonly class NativeCalculator extends Calculator } $sum = (string) $sum; - $sumLength = \strlen($sum); + $sumLength = strlen($sum); if ($sumLength < $blockLength) { - $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; + $sum = str_repeat('0', $blockLength - $sumLength) . $sum; } $result = $sum . $result; @@ -395,7 +410,7 @@ final readonly class NativeCalculator extends Calculator // Carry cannot be 1 when the loop ends, as a > b assert($carry === 0); - $result = \ltrim($result, '0'); + $result = ltrim($result, '0'); if ($invert) { $result = $this->neg($result); @@ -409,17 +424,17 @@ final readonly class NativeCalculator extends Calculator * * @pure */ - private function doMul(string $a, string $b) : string + private function doMul(string $a, string $b): string { - $x = \strlen($a); - $y = \strlen($b); + $x = strlen($a); + $y = strlen($b); - $maxDigits = \intdiv($this->maxDigits, 2); + $maxDigits = intdiv($this->maxDigits, 2); $complement = 10 ** $maxDigits; $result = '0'; - for ($i = $x - $maxDigits;; $i -= $maxDigits) { + for ($i = $x - $maxDigits; ; $i -= $maxDigits) { $blockALength = $maxDigits; if ($i < 0) { @@ -427,12 +442,12 @@ final readonly class NativeCalculator extends Calculator $i = 0; } - $blockA = (int) \substr($a, $i, $blockALength); + $blockA = (int) substr($a, $i, $blockALength); $line = ''; $carry = 0; - for ($j = $y - $maxDigits;; $j -= $maxDigits) { + for ($j = $y - $maxDigits; ; $j -= $maxDigits) { $blockBLength = $maxDigits; if ($j < 0) { @@ -440,14 +455,14 @@ final readonly class NativeCalculator extends Calculator $j = 0; } - $blockB = (int) \substr($b, $j, $blockBLength); + $blockB = (int) substr($b, $j, $blockBLength); $mul = $blockA * $blockB + $carry; $value = $mul % $complement; $carry = ($mul - $value) / $complement; $value = (string) $value; - $value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT); + $value = str_pad($value, $maxDigits, '0', STR_PAD_LEFT); $line = $value . $line; @@ -460,10 +475,10 @@ final readonly class NativeCalculator extends Calculator $line = $carry . $line; } - $line = \ltrim($line, '0'); + $line = ltrim($line, '0'); if ($line !== '') { - $line .= \str_repeat('0', $x - $blockALength - $i); + $line .= str_repeat('0', $x - $blockALength - $i); $result = $this->add($result, $line); } @@ -482,7 +497,7 @@ final readonly class NativeCalculator extends Calculator * * @pure */ - private function doDiv(string $a, string $b) : array + private function doDiv(string $a, string $b): array { $cmp = $this->doCmp($a, $b); @@ -490,8 +505,8 @@ final readonly class NativeCalculator extends Calculator return ['0', $a]; } - $x = \strlen($a); - $y = \strlen($b); + $x = strlen($a); + $y = strlen($b); // we now know that a >= b && x >= y @@ -503,20 +518,20 @@ final readonly class NativeCalculator extends Calculator $nb = $b * 1; // cast to number // performance optimization in cases where the remainder will never cause int overflow if (is_int(($nb - 1) * 10 + 9)) { - $r = (int) \substr($a, 0, $z - 1); + $r = (int) substr($a, 0, $z - 1); for ($i = $z - 1; $i < $x; $i++) { $n = $r * 10 + (int) $a[$i]; /** @var int $nb */ - $q .= \intdiv($n, $nb); + $q .= intdiv($n, $nb); $r = $n % $nb; } - return [\ltrim($q, '0') ?: '0', (string) $r]; + return [ltrim($q, '0') ?: '0', (string) $r]; } - for (;;) { - $focus = \substr($a, 0, $z); + for (; ;) { + $focus = substr($a, 0, $z); $cmp = $this->doCmp($focus, $b); @@ -528,7 +543,7 @@ final readonly class NativeCalculator extends Calculator $z++; } - $zeros = \str_repeat('0', $x - $z); + $zeros = str_repeat('0', $x - $z); $q = $this->add($q, '1' . $zeros); $a = $this->sub($a, $b . $zeros); @@ -539,7 +554,7 @@ final readonly class NativeCalculator extends Calculator break; } - $x = \strlen($a); + $x = strlen($a); if ($x < $y) { // remainder < dividend break; @@ -558,10 +573,10 @@ final readonly class NativeCalculator extends Calculator * * @pure */ - private function doCmp(string $a, string $b) : int + private function doCmp(string $a, string $b): int { - $x = \strlen($a); - $y = \strlen($b); + $x = strlen($a); + $y = strlen($b); $cmp = $x <=> $y; @@ -569,7 +584,7 @@ final readonly class NativeCalculator extends Calculator return $cmp; } - return \strcmp($a, $b) <=> 0; // enforce -1|0|1 + return strcmp($a, $b) <=> 0; // enforce -1|0|1 } /** @@ -581,19 +596,19 @@ final readonly class NativeCalculator extends Calculator * * @pure */ - private function pad(string $a, string $b) : array + private function pad(string $a, string $b): array { - $x = \strlen($a); - $y = \strlen($b); + $x = strlen($a); + $y = strlen($b); if ($x > $y) { - $b = \str_repeat('0', $x - $y) . $b; + $b = str_repeat('0', $x - $y) . $b; return [$a, $b, $x]; } if ($x < $y) { - $a = \str_repeat('0', $y - $x) . $a; + $a = str_repeat('0', $y - $x) . $a; return [$a, $b, $y]; } diff --git a/vendor/brick/math/src/Internal/CalculatorRegistry.php b/vendor/brick/math/src/Internal/CalculatorRegistry.php index 394fae69a..859d08a29 100644 --- a/vendor/brick/math/src/Internal/CalculatorRegistry.php +++ b/vendor/brick/math/src/Internal/CalculatorRegistry.php @@ -25,7 +25,7 @@ final class CalculatorRegistry * * @param Calculator|null $calculator The calculator instance, or null to revert to autodetect. */ - final public static function set(?Calculator $calculator) : void + final public static function set(?Calculator $calculator): void { self::$instance = $calculator; } @@ -40,7 +40,7 @@ final class CalculatorRegistry * * @pure */ - final public static function get() : Calculator + final public static function get(): Calculator { /** @phpstan-ignore impure.staticPropertyAccess */ if (self::$instance === null) { @@ -56,9 +56,10 @@ final class CalculatorRegistry * Returns the fastest available Calculator implementation. * * @pure + * * @codeCoverageIgnore */ - private static function detect() : Calculator + private static function detect(): Calculator { if (extension_loaded('gmp')) { return new Calculator\GmpCalculator(); diff --git a/vendor/brick/math/tools/ecs/composer.json b/vendor/brick/math/tools/ecs/composer.json new file mode 100644 index 000000000..e70d633d7 --- /dev/null +++ b/vendor/brick/math/tools/ecs/composer.json @@ -0,0 +1,10 @@ +{ + "require": { + "brick/coding-standard": "v2" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": false + } + } +} diff --git a/vendor/brick/math/tools/ecs/ecs.php b/vendor/brick/math/tools/ecs/ecs.php new file mode 100644 index 000000000..45e70bc65 --- /dev/null +++ b/vendor/brick/math/tools/ecs/ecs.php @@ -0,0 +1,33 @@ +import(__DIR__ . '/vendor/brick/coding-standard/ecs.php'); + + $libRootPath = realpath(__DIR__ . '/../..'); + + $ecsConfig->paths( + [ + $libRootPath . '/src', + $libRootPath . '/tests', + $libRootPath . '/phpunit.php', + $libRootPath . '/random-tests.php', + __FILE__, + ], + ); + + $ecsConfig->skip([ + // Allows alignment in test providers + DuplicateSpacesSniff::class => [$libRootPath . '/tests'], + + // We want to keep BigNumber|int|float|string order + OrderedTypesFixer::class => null, + PhpdocTypesOrderFixer::class => null, + ]); +}; diff --git a/vendor/chillerlan/php-qrcode/composer.json b/vendor/chillerlan/php-qrcode/composer.json index 84aeba2ea..b0ab174f6 100644 --- a/vendor/chillerlan/php-qrcode/composer.json +++ b/vendor/chillerlan/php-qrcode/composer.json @@ -54,7 +54,7 @@ "require-dev": { "ext-fileinfo": "*", "chillerlan/php-authenticator": "^4.3.1 || ^5.2.1", - "phan/phan": "^5.5.1", + "phan/phan": "^5.5.2", "phpcompatibility/php-compatibility": "10.x-dev", "phpunit/phpunit": "^9.6", "phpmd/phpmd": "^2.15", @@ -79,7 +79,7 @@ }, "scripts": { "phan": "@php vendor/bin/phan", - "phpcs": "@php vendor/bin/phpcs", + "phpcs": "@php vendor/bin/phpcs -v", "phpmd": "@php vendor/bin/phpmd src text ./phpmd.xml.dist", "phpunit": "@php vendor/bin/phpunit" }, diff --git a/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php b/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php index 4023e74d2..7360a1db9 100644 --- a/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php +++ b/vendor/chillerlan/php-qrcode/src/Common/GenericGFPoly.php @@ -38,7 +38,7 @@ final class GenericGFPoly{ public function __construct(array $coefficients, ?int $degree = null){ $degree ??= 0; - if(empty($coefficients)){ + if($coefficients === []){ throw new QRCodeException('arg $coefficients is empty'); } diff --git a/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php b/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php index ef0f245d9..b0d605e8e 100644 --- a/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php +++ b/vendor/chillerlan/php-qrcode/src/Decoder/DecoderResult.php @@ -46,7 +46,7 @@ final class DecoderResult{ */ public function __construct(?iterable $properties = null){ - if(!empty($properties)){ + if($properties !== null){ foreach($properties as $property => $value){ diff --git a/vendor/chillerlan/php-qrcode/src/Detector/Detector.php b/vendor/chillerlan/php-qrcode/src/Detector/Detector.php index e173d8b6c..eb48f4ffb 100644 --- a/vendor/chillerlan/php-qrcode/src/Detector/Detector.php +++ b/vendor/chillerlan/php-qrcode/src/Detector/Detector.php @@ -56,7 +56,7 @@ final class Detector{ $alignmentPattern = null; // Anything above version 1 has an alignment pattern - if(!empty($provisionalVersion->getAlignmentPattern())){ + if($provisionalVersion->getAlignmentPattern() !== []){ // Guess where a "bottom right" finder pattern would have been $bottomRightX = ($topRight->getX() - $topLeft->getX() + $bottomLeft->getX()); $bottomRightY = ($topRight->getY() - $topLeft->getY() + $bottomLeft->getY()); diff --git a/vendor/chillerlan/php-qrcode/src/Output/QREps.php b/vendor/chillerlan/php-qrcode/src/Output/QREps.php index 76dd50b37..cef56d785 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QREps.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QREps.php @@ -130,11 +130,12 @@ class QREps extends QROutputAbstract{ } // create the path elements + /** @phan-suppress-next-line PhanDeprecatedFunction */ $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); foreach($paths as $M_TYPE => $path){ - if(empty($path)){ + if($path === []){ continue; } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php index 25db1c902..b3d33807d 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php @@ -17,7 +17,7 @@ use chillerlan\Settings\SettingsContainerInterface; use ErrorException; use Throwable; use function array_values, count, extension_loaded, imagebmp, imagecolorallocate, imagecolortransparent, - imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, + imagecreatetruecolor, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, imagescale, imagetypes, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start, restore_error_handler, set_error_handler, sprintf; use const IMG_BMP, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP; @@ -378,7 +378,6 @@ class QRGdImage extends QROutputAbstract{ $this->renderImage(); $imageData = ob_get_contents(); - imagedestroy($this->image); } // not going to cover edge cases // @codeCoverageIgnoreStart diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php index 735c4180b..9cf604842 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QRMarkupSVG.php @@ -74,7 +74,7 @@ class QRMarkupSVG extends QRMarkup{ protected function createMarkup(bool $saveToFile):string{ $svg = $this->header(); - if(!empty($this->options->svgDefs)){ + if($this->options->svgDefs !== ''){ $svg .= sprintf('%1$s%2$s%2$s', $this->options->svgDefs, $this->eol); } @@ -129,6 +129,7 @@ class QRMarkupSVG extends QRMarkup{ * returns one or more SVG elements */ protected function paths():string{ + /** @phan-suppress-next-line PhanDeprecatedFunction */ $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE)); $svg = []; @@ -144,7 +145,7 @@ class QRMarkupSVG extends QRMarkup{ $path = implode($this->eol, $chonks); - if(empty($path)){ + if($path === ''){ continue; } diff --git a/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php index a2757ac8c..e1457a04e 100644 --- a/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php +++ b/vendor/chillerlan/php-qrcode/src/Output/QROutputAbstract.php @@ -225,6 +225,10 @@ abstract class QROutputAbstract implements QROutputInterface{ * $y - current row * $M_TYPE - field value * $M_TYPE_LAYER - (possibly modified) field value that acts as layer id + * + * @deprecated 5.0.5 The parameter $transform will be removed in the next major version + * in favor of a concrete method QROutputAbstract::moduleTransform() + * @see \chillerlan\QRCode\Output\QROutputAbstract::moduleTransform() */ protected function collectModules(Closure $transform):array{ $paths = []; @@ -258,4 +262,22 @@ abstract class QROutputAbstract implements QROutputInterface{ return $paths; } + /** + * The transform callback for the module collector + * + * $x - current column + * $y - current row + * $M_TYPE - field value + * $M_TYPE_LAYER - (possibly modified) field value that acts as layer id ($paths array key) + * + * This method should return a value suitable for the current output class. + * It must return `null` for an empty value. + * + * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules() + * @return mixed|null + */ + protected function moduleTransform(int $x, int $y, int $M_TYPE, int $M_TYPE_LAYER){ + return null; + } + } diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 643745bc9..d643996b9 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -451,8 +451,10 @@ return array( 'League\\HTMLToMarkdown\\PreConverterInterface' => $vendorDir . '/league/html-to-markdown/src/PreConverterInterface.php', 'League\\Uri\\BaseUri' => $vendorDir . '/league/uri/BaseUri.php', 'League\\Uri\\Contracts\\AuthorityInterface' => $vendorDir . '/league/uri-interfaces/Contracts/AuthorityInterface.php', + 'League\\Uri\\Contracts\\Conditionable' => $vendorDir . '/league/uri-interfaces/Contracts/Conditionable.php', 'League\\Uri\\Contracts\\DataPathInterface' => $vendorDir . '/league/uri-interfaces/Contracts/DataPathInterface.php', 'League\\Uri\\Contracts\\DomainHostInterface' => $vendorDir . '/league/uri-interfaces/Contracts/DomainHostInterface.php', + 'League\\Uri\\Contracts\\FragmentDirective' => $vendorDir . '/league/uri-interfaces/Contracts/FragmentDirective.php', 'League\\Uri\\Contracts\\FragmentInterface' => $vendorDir . '/league/uri-interfaces/Contracts/FragmentInterface.php', 'League\\Uri\\Contracts\\HostInterface' => $vendorDir . '/league/uri-interfaces/Contracts/HostInterface.php', 'League\\Uri\\Contracts\\IpHostInterface' => $vendorDir . '/league/uri-interfaces/Contracts/IpHostInterface.php', @@ -485,9 +487,12 @@ return array( 'League\\Uri\\Idna\\Result' => $vendorDir . '/league/uri-interfaces/Idna/Result.php', 'League\\Uri\\KeyValuePair\\Converter' => $vendorDir . '/league/uri-interfaces/KeyValuePair/Converter.php', 'League\\Uri\\QueryString' => $vendorDir . '/league/uri-interfaces/QueryString.php', + 'League\\Uri\\SchemeType' => $vendorDir . '/league/uri/SchemeType.php', 'League\\Uri\\Uri' => $vendorDir . '/league/uri/Uri.php', + 'League\\Uri\\UriComparisonMode' => $vendorDir . '/league/uri-interfaces/UriComparisonMode.php', 'League\\Uri\\UriInfo' => $vendorDir . '/league/uri/UriInfo.php', 'League\\Uri\\UriResolver' => $vendorDir . '/league/uri/UriResolver.php', + 'League\\Uri\\UriScheme' => $vendorDir . '/league/uri/UriScheme.php', 'League\\Uri\\UriString' => $vendorDir . '/league/uri-interfaces/UriString.php', 'League\\Uri\\UriTemplate' => $vendorDir . '/league/uri/UriTemplate.php', 'League\\Uri\\UriTemplate\\Expression' => $vendorDir . '/league/uri/UriTemplate/Expression.php', @@ -496,6 +501,8 @@ return array( 'League\\Uri\\UriTemplate\\TemplateCanNotBeExpanded' => $vendorDir . '/league/uri/UriTemplate/TemplateCanNotBeExpanded.php', 'League\\Uri\\UriTemplate\\VarSpecifier' => $vendorDir . '/league/uri/UriTemplate/VarSpecifier.php', 'League\\Uri\\UriTemplate\\VariableBag' => $vendorDir . '/league/uri/UriTemplate/VariableBag.php', + 'League\\Uri\\Urn' => $vendorDir . '/league/uri/Urn.php', + 'League\\Uri\\UrnComparisonMode' => $vendorDir . '/league/uri-interfaces/UrnComparisonMode.php', 'Mdanter\\Ecc\\Crypto\\EcDH\\EcDH' => $vendorDir . '/paragonie/ecc/src/Crypto/EcDH/EcDH.php', 'Mdanter\\Ecc\\Crypto\\EcDH\\EcDHInterface' => $vendorDir . '/paragonie/ecc/src/Crypto/EcDH/EcDHInterface.php', 'Mdanter\\Ecc\\Crypto\\Key\\PrivateKey' => $vendorDir . '/paragonie/ecc/src/Crypto/Key/PrivateKey.php', diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php index c2d09dfb4..d057a68a1 100644 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -8,13 +8,13 @@ $baseDir = dirname($vendorDir); return array( '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php', '3109cb1a231dcd04bee1f9f620d46975' => $vendorDir . '/paragonie/sodium_compat/autoload.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php', 'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php', 'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php', '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php', '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '3253b43614197c132b67e5f343def5b7' => $vendorDir . '/paragonie/easy-ecc/autoload-shim.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index c8796e03b..e7488fbe3 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -9,13 +9,13 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d public static $files = array ( '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php', '3109cb1a231dcd04bee1f9f620d46975' => __DIR__ . '/..' . '/paragonie/sodium_compat/autoload.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php', 'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php', 'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php', '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php', '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '3253b43614197c132b67e5f343def5b7' => __DIR__ . '/..' . '/paragonie/easy-ecc/autoload-shim.php', @@ -790,8 +790,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d 'League\\HTMLToMarkdown\\PreConverterInterface' => __DIR__ . '/..' . '/league/html-to-markdown/src/PreConverterInterface.php', 'League\\Uri\\BaseUri' => __DIR__ . '/..' . '/league/uri/BaseUri.php', 'League\\Uri\\Contracts\\AuthorityInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/AuthorityInterface.php', + 'League\\Uri\\Contracts\\Conditionable' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/Conditionable.php', 'League\\Uri\\Contracts\\DataPathInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/DataPathInterface.php', 'League\\Uri\\Contracts\\DomainHostInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/DomainHostInterface.php', + 'League\\Uri\\Contracts\\FragmentDirective' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/FragmentDirective.php', 'League\\Uri\\Contracts\\FragmentInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/FragmentInterface.php', 'League\\Uri\\Contracts\\HostInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/HostInterface.php', 'League\\Uri\\Contracts\\IpHostInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/IpHostInterface.php', @@ -824,9 +826,12 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d 'League\\Uri\\Idna\\Result' => __DIR__ . '/..' . '/league/uri-interfaces/Idna/Result.php', 'League\\Uri\\KeyValuePair\\Converter' => __DIR__ . '/..' . '/league/uri-interfaces/KeyValuePair/Converter.php', 'League\\Uri\\QueryString' => __DIR__ . '/..' . '/league/uri-interfaces/QueryString.php', + 'League\\Uri\\SchemeType' => __DIR__ . '/..' . '/league/uri/SchemeType.php', 'League\\Uri\\Uri' => __DIR__ . '/..' . '/league/uri/Uri.php', + 'League\\Uri\\UriComparisonMode' => __DIR__ . '/..' . '/league/uri-interfaces/UriComparisonMode.php', 'League\\Uri\\UriInfo' => __DIR__ . '/..' . '/league/uri/UriInfo.php', 'League\\Uri\\UriResolver' => __DIR__ . '/..' . '/league/uri/UriResolver.php', + 'League\\Uri\\UriScheme' => __DIR__ . '/..' . '/league/uri/UriScheme.php', 'League\\Uri\\UriString' => __DIR__ . '/..' . '/league/uri-interfaces/UriString.php', 'League\\Uri\\UriTemplate' => __DIR__ . '/..' . '/league/uri/UriTemplate.php', 'League\\Uri\\UriTemplate\\Expression' => __DIR__ . '/..' . '/league/uri/UriTemplate/Expression.php', @@ -835,6 +840,8 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d 'League\\Uri\\UriTemplate\\TemplateCanNotBeExpanded' => __DIR__ . '/..' . '/league/uri/UriTemplate/TemplateCanNotBeExpanded.php', 'League\\Uri\\UriTemplate\\VarSpecifier' => __DIR__ . '/..' . '/league/uri/UriTemplate/VarSpecifier.php', 'League\\Uri\\UriTemplate\\VariableBag' => __DIR__ . '/..' . '/league/uri/UriTemplate/VariableBag.php', + 'League\\Uri\\Urn' => __DIR__ . '/..' . '/league/uri/Urn.php', + 'League\\Uri\\UrnComparisonMode' => __DIR__ . '/..' . '/league/uri-interfaces/UrnComparisonMode.php', 'Mdanter\\Ecc\\Crypto\\EcDH\\EcDH' => __DIR__ . '/..' . '/paragonie/ecc/src/Crypto/EcDH/EcDH.php', 'Mdanter\\Ecc\\Crypto\\EcDH\\EcDHInterface' => __DIR__ . '/..' . '/paragonie/ecc/src/Crypto/EcDH/EcDHInterface.php', 'Mdanter\\Ecc\\Crypto\\Key\\PrivateKey' => __DIR__ . '/..' . '/paragonie/ecc/src/Crypto/Key/PrivateKey.php', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 9e8073e55..032ffbb57 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -155,17 +155,17 @@ }, { "name": "brick/math", - "version": "0.14.0", - "version_normalized": "0.14.0.0", + "version": "0.14.1", + "version_normalized": "0.14.1.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { @@ -176,7 +176,7 @@ "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, - "time": "2025-08-29T12:40:03+00:00", + "time": "2025-11-24T14:40:29+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -206,7 +206,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.0" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -283,17 +283,17 @@ }, { "name": "chillerlan/php-qrcode", - "version": "5.0.4", - "version_normalized": "5.0.4.0", + "version": "5.0.5", + "version_normalized": "5.0.5.0", "source": { "type": "git", "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43" + "reference": "7b66282572fc14075c0507d74d9837dab25b38d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/390393e97a6e42ccae0e0d6205b8d4200f7ddc43", - "reference": "390393e97a6e42ccae0e0d6205b8d4200f7ddc43", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/7b66282572fc14075c0507d74d9837dab25b38d6", + "reference": "7b66282572fc14075c0507d74d9837dab25b38d6", "shasum": "" }, "require": { @@ -304,7 +304,7 @@ "require-dev": { "chillerlan/php-authenticator": "^4.3.1 || ^5.2.1", "ext-fileinfo": "*", - "phan/phan": "^5.5.1", + "phan/phan": "^5.5.2", "phpcompatibility/php-compatibility": "10.x-dev", "phpmd/phpmd": "^2.15", "phpunit/phpunit": "^9.6", @@ -317,7 +317,7 @@ "setasign/fpdf": "Required to use the QR FPDF output.", "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" }, - "time": "2025-09-19T17:30:27+00:00", + "time": "2025-11-23T23:51:44+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1046,37 +1046,42 @@ }, { "name": "league/uri", - "version": "7.5.1", - "version_normalized": "7.5.1.0", + "version": "7.6.0", + "version_normalized": "7.6.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "81fb5145d2644324614cc532b28efd0215bda430" + "reference": "f625804987a0a9112d954f9209d91fec52182344" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", - "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/f625804987a0a9112d954f9209d91fec52182344", + "reference": "f625804987a0a9112d954f9209d91fec52182344", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.5", - "php": "^8.1" + "league/uri-interfaces": "^7.6", + "php": "^8.1", + "psr/http-factory": "^1" }, "conflict": { "league/uri-schemes": "^1.0" }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components": "Needed to easily manipulate URI objects components", + "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, - "time": "2024-12-08T08:40:02+00:00", + "time": "2025-11-18T12:17:23+00:00", "type": "library", "extra": { "branch-alias": { @@ -1103,6 +1108,7 @@ "description": "URI manipulation library", "homepage": "https://uri.thephpleague.com", "keywords": [ + "URN", "data-uri", "file-uri", "ftp", @@ -1115,9 +1121,11 @@ "psr-7", "query-string", "querystring", + "rfc2141", "rfc3986", "rfc3987", "rfc6570", + "rfc8141", "uri", "uri-template", "url", @@ -1127,7 +1135,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.5.1" + "source": "https://github.com/thephpleague/uri/tree/7.6.0" }, "funding": [ { @@ -1139,23 +1147,22 @@ }, { "name": "league/uri-interfaces", - "version": "7.5.0", - "version_normalized": "7.5.0.0", + "version": "7.6.0", + "version_normalized": "7.6.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", - "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ccbfb51c0445298e7e0b7f4481b942f589665368", + "reference": "ccbfb51c0445298e7e0b7f4481b942f589665368", "shasum": "" }, "require": { "ext-filter": "*", "php": "^8.1", - "psr/http-factory": "^1", "psr/http-message": "^1.1 || ^2.0" }, "suggest": { @@ -1163,9 +1170,10 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle WHATWG URL", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, - "time": "2024-12-08T08:18:47+00:00", + "time": "2025-11-18T12:17:23+00:00", "type": "library", "extra": { "branch-alias": { @@ -1189,7 +1197,7 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interfaces and classes for URI representation and interaction", + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", "homepage": "https://uri.thephpleague.com", "keywords": [ "data-uri", @@ -1214,7 +1222,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.6.0" }, "funding": [ { @@ -1265,17 +1273,17 @@ }, { "name": "macgirvin/http-message-signer", - "version": "v0.2.8", - "version_normalized": "0.2.8.0", + "version": "v0.2.12", + "version_normalized": "0.2.12.0", "source": { "type": "git", "url": "https://github.com/macgirvin/HTTP-Message-Signer.git", - "reference": "8d70246ff06a6d2128d37069e7ea801f4b623dad" + "reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/8d70246ff06a6d2128d37069e7ea801f4b623dad", - "reference": "8d70246ff06a6d2128d37069e7ea801f4b623dad", + "url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/6e5b25a5536576e5046f5b0a7db620b5eb451453", + "reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453", "shasum": "" }, "require": { @@ -1291,7 +1299,7 @@ "require-dev": { "phpunit/phpunit": "^10.0" }, - "time": "2025-10-07T19:56:11+00:00", + "time": "2025-12-02T18:48:05+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1306,7 +1314,7 @@ "description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests", "support": { "issues": "https://github.com/macgirvin/HTTP-Message-Signer/issues", - "source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.8" + "source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.12" }, "install-path": "../macgirvin/http-message-signer" }, @@ -2942,52 +2950,47 @@ }, { "name": "scssphp/scssphp", - "version": "v2.0.1", - "version_normalized": "2.0.1.0", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", "source": { "type": "git", "url": "https://github.com/scssphp/scssphp.git", - "reference": "024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f" + "reference": "d8450c2baf5fb07d00374999d0ea51276974d1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scssphp/scssphp/zipball/024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f", - "reference": "024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f", + "url": "https://api.github.com/repos/scssphp/scssphp/zipball/d8450c2baf5fb07d00374999d0ea51276974d1b6", + "reference": "d8450c2baf5fb07d00374999d0ea51276974d1b6", "shasum": "" }, "require": { "ext-ctype": "*", "ext-json": "*", - "league/uri": "^7.4", - "league/uri-interfaces": "^7.4", + "ext-mbstring": "*", + "league/uri": "^7.6", + "league/uri-interfaces": "^7.6", "php": ">=8.1", - "scssphp/source-span": "^1.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-mbstring": "^1.30" + "scssphp/source-span": "^1.1", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4", + "jgthms/bulma": "~0.9.4", + "jiripudil/phpstan-sealed-classes": "^1.3", + "phpstan/phpstan": "^2.1.31", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^9.5.6", "sass/sass-spec": "*", - "squizlabs/php_codesniffer": "~3.5", - "symfony/phpunit-bridge": "^5.1", - "symfony/var-dumper": "^6.3", + "squizlabs/php_codesniffer": "^3.13", + "symfony/phpunit-bridge": "^7.3 || ^8.0", + "symfony/polyfill-php84": "^1.33", + "symfony/var-dumper": "^6.4 || ^7.3 || ^8.0", "thoughtbot/bourbon": "^7.0", - "twbs/bootstrap": "~5.0", + "twbs/bootstrap": "^5.3", "twbs/bootstrap4": "4.6.1", "zurb/foundation": "~6.7.0" }, - "suggest": { - "ext-mbstring": "For best performance, mbstring should be installed as it is faster than the polyfill" - }, - "time": "2025-01-31T12:28:20+00:00", + "time": "2025-11-21T17:27:59+00:00", "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": false, - "forward-command": false - } - }, "installation-source": "dist", "autoload": { "psr-4": { @@ -3011,7 +3014,7 @@ } ], "description": "scssphp is a compiler for SCSS written in PHP.", - "homepage": "http://scssphp.github.io/scssphp/", + "homepage": "https://scssphp.github.io/scssphp/", "keywords": [ "css", "less", @@ -3021,28 +3024,29 @@ ], "support": { "issues": "https://github.com/scssphp/scssphp/issues", - "source": "https://github.com/scssphp/scssphp/tree/v2.0.1" + "source": "https://github.com/scssphp/scssphp/tree/v2.1.0" }, "install-path": "../scssphp/scssphp" }, { "name": "scssphp/source-span", - "version": "v1.0.0", - "version_normalized": "1.0.0.0", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", "source": { "type": "git", "url": "https://github.com/scssphp/source-span.git", - "reference": "f08fc78765e6fb6fa8ca0573fc61b3f8860f0114" + "reference": "37d653206daf11da1ee60b333984101bc4c27ba2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scssphp/source-span/zipball/f08fc78765e6fb6fa8ca0573fc61b3f8860f0114", - "reference": "f08fc78765e6fb6fa8ca0573fc61b3f8860f0114", + "url": "https://api.github.com/repos/scssphp/source-span/zipball/37d653206daf11da1ee60b333984101bc4c27ba2", + "reference": "37d653206daf11da1ee60b333984101bc4c27ba2", "shasum": "" }, "require": { - "league/uri": "^7.4", - "league/uri-interfaces": "^7.4", + "ext-mbstring": "*", + "league/uri": "^7.6", + "league/uri-interfaces": "^7.6", "php": ">=8.1" }, "require-dev": { @@ -3050,10 +3054,10 @@ "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^9.5.6", "squizlabs/php_codesniffer": "~3.5", - "symfony/phpunit-bridge": "^5.1", - "symfony/var-dumper": "^6.3" + "symfony/phpunit-bridge": "^6.4 || ^7.3 || ^8.0", + "symfony/var-dumper": "^6.4 || ^7.3 || ^8.0" }, - "time": "2024-12-09T23:08:15+00:00", + "time": "2025-11-21T16:28:19+00:00", "type": "library", "extra": { "branch-alias": { @@ -3082,7 +3086,7 @@ ], "support": { "issues": "https://github.com/scssphp/source-span/issues", - "source": "https://github.com/scssphp/source-span/tree/v1.0.0" + "source": "https://github.com/scssphp/source-span/tree/v1.1.0" }, "install-path": "../scssphp/source-span" }, @@ -3462,17 +3466,17 @@ }, { "name": "symfony/filesystem", - "version": "v7.3.6", - "version_normalized": "7.3.6.0", + "version": "v7.4.0", + "version_normalized": "7.4.0.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", "shasum": "" }, "require": { @@ -3481,9 +3485,9 @@ "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^6.4|^7.0|^8.0" }, - "time": "2025-11-05T09:52:27+00:00", + "time": "2025-11-27T13:27:24+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3511,7 +3515,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.6" + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" }, "funding": [ { diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 2dc84f021..1fdbae1da 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'zotlabs/hubzilla', 'pretty_version' => 'dev-10.6RC', 'version' => 'dev-10.6RC', - 'reference' => '57c22f4d0f70650b67db1ed5068fee8ca0410a5f', + 'reference' => 'a0e6dcbb773532078915c580a6be3b677ce9941a', 'type' => 'application', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -29,9 +29,9 @@ 'dev_requirement' => false, ), 'brick/math' => array( - 'pretty_version' => '0.14.0', - 'version' => '0.14.0.0', - 'reference' => '113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2', + 'pretty_version' => '0.14.1', + 'version' => '0.14.1.0', + 'reference' => 'f05858549e5f9d7bb45875a75583240a38a281d0', 'type' => 'library', 'install_path' => __DIR__ . '/../brick/math', 'aliases' => array(), @@ -47,9 +47,9 @@ 'dev_requirement' => false, ), 'chillerlan/php-qrcode' => array( - 'pretty_version' => '5.0.4', - 'version' => '5.0.4.0', - 'reference' => '390393e97a6e42ccae0e0d6205b8d4200f7ddc43', + 'pretty_version' => '5.0.5', + 'version' => '5.0.5.0', + 'reference' => '7b66282572fc14075c0507d74d9837dab25b38d6', 'type' => 'library', 'install_path' => __DIR__ . '/../chillerlan/php-qrcode', 'aliases' => array(), @@ -137,18 +137,18 @@ 'dev_requirement' => false, ), 'league/uri' => array( - 'pretty_version' => '7.5.1', - 'version' => '7.5.1.0', - 'reference' => '81fb5145d2644324614cc532b28efd0215bda430', + 'pretty_version' => '7.6.0', + 'version' => '7.6.0.0', + 'reference' => 'f625804987a0a9112d954f9209d91fec52182344', 'type' => 'library', 'install_path' => __DIR__ . '/../league/uri', 'aliases' => array(), 'dev_requirement' => false, ), 'league/uri-interfaces' => array( - 'pretty_version' => '7.5.0', - 'version' => '7.5.0.0', - 'reference' => '08cfc6c4f3d811584fb09c37e2849e6a7f9b0742', + 'pretty_version' => '7.6.0', + 'version' => '7.6.0.0', + 'reference' => 'ccbfb51c0445298e7e0b7f4481b942f589665368', 'type' => 'library', 'install_path' => __DIR__ . '/../league/uri-interfaces', 'aliases' => array(), @@ -164,9 +164,9 @@ 'dev_requirement' => false, ), 'macgirvin/http-message-signer' => array( - 'pretty_version' => 'v0.2.8', - 'version' => '0.2.8.0', - 'reference' => '8d70246ff06a6d2128d37069e7ea801f4b623dad', + 'pretty_version' => 'v0.2.12', + 'version' => '0.2.12.0', + 'reference' => '6e5b25a5536576e5046f5b0a7db620b5eb451453', 'type' => 'library', 'install_path' => __DIR__ . '/../macgirvin/http-message-signer', 'aliases' => array(), @@ -410,18 +410,18 @@ 'dev_requirement' => false, ), 'scssphp/scssphp' => array( - 'pretty_version' => 'v2.0.1', - 'version' => '2.0.1.0', - 'reference' => '024f92cd9782e3033b41c2d1c66ab1c0e5c12c0f', + 'pretty_version' => 'v2.1.0', + 'version' => '2.1.0.0', + 'reference' => 'd8450c2baf5fb07d00374999d0ea51276974d1b6', 'type' => 'library', 'install_path' => __DIR__ . '/../scssphp/scssphp', 'aliases' => array(), 'dev_requirement' => false, ), 'scssphp/source-span' => array( - 'pretty_version' => 'v1.0.0', - 'version' => '1.0.0.0', - 'reference' => 'f08fc78765e6fb6fa8ca0573fc61b3f8860f0114', + 'pretty_version' => 'v1.1.0', + 'version' => '1.1.0.0', + 'reference' => '37d653206daf11da1ee60b333984101bc4c27ba2', 'type' => 'library', 'install_path' => __DIR__ . '/../scssphp/source-span', 'aliases' => array(), @@ -473,9 +473,9 @@ 'dev_requirement' => false, ), 'symfony/filesystem' => array( - 'pretty_version' => 'v7.3.6', - 'version' => '7.3.6.0', - 'reference' => 'e9bcfd7837928ab656276fe00464092cc9e1826a', + 'pretty_version' => 'v7.4.0', + 'version' => '7.4.0.0', + 'reference' => 'd551b38811096d0be9c4691d406991b47c0c630a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), @@ -544,7 +544,7 @@ 'zotlabs/hubzilla' => array( 'pretty_version' => 'dev-10.6RC', 'version' => 'dev-10.6RC', - 'reference' => '57c22f4d0f70650b67db1ed5068fee8ca0410a5f', + 'reference' => 'a0e6dcbb773532078915c580a6be3b677ce9941a', 'type' => 'application', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/vendor/league/uri-interfaces/Contracts/Conditionable.php b/vendor/league/uri-interfaces/Contracts/Conditionable.php new file mode 100644 index 000000000..385a78cc4 --- /dev/null +++ b/vendor/league/uri-interfaces/Contracts/Conditionable.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri\Contracts; + +interface Conditionable +{ + /** + * Apply the callback if the given "condition" is (or resolves to) true. + * + * @param (callable(static): bool)|bool $condition + * @param callable(static): (static|null) $onSuccess + * @param ?callable(static): (static|null) $onFail + */ + public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static; +} diff --git a/vendor/league/uri-interfaces/Contracts/FragmentDirective.php b/vendor/league/uri-interfaces/Contracts/FragmentDirective.php new file mode 100644 index 000000000..11742fa8b --- /dev/null +++ b/vendor/league/uri-interfaces/Contracts/FragmentDirective.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri\Contracts; + +use Stringable; + +/** + * @see https://wicg.github.io/scroll-to-text-fragment/#the-fragment-directive + */ +interface FragmentDirective extends Stringable +{ + /** + * The decoded Directive name. + * + * @return non-empty-string + */ + public function name(): string; + + /** + * The decoded Directive value. + */ + public function value(): ?string; + + /** + * The encoded string representation of the fragment. + */ + public function toString(): string; + + /** + * The encoded string representation of the fragment using + * the Stringable interface. + * + * @see FragmentDirective::toString() + */ + public function __toString(): string; + + /** + * Tells whether the submitted value is equals to the string + * representation of the given directive. + */ + public function equals(mixed $directive): bool; +} diff --git a/vendor/league/uri-interfaces/Contracts/FragmentInterface.php b/vendor/league/uri-interfaces/Contracts/FragmentInterface.php index 3d80f0661..440a08741 100644 --- a/vendor/league/uri-interfaces/Contracts/FragmentInterface.php +++ b/vendor/league/uri-interfaces/Contracts/FragmentInterface.php @@ -13,6 +13,9 @@ declare(strict_types=1); namespace League\Uri\Contracts; +/** + * @method self normalize() returns the normalized string representation of the component + */ interface FragmentInterface extends UriComponentInterface { /** diff --git a/vendor/league/uri-interfaces/Contracts/HostInterface.php b/vendor/league/uri-interfaces/Contracts/HostInterface.php index 16212bfe1..48d8abea3 100644 --- a/vendor/league/uri-interfaces/Contracts/HostInterface.php +++ b/vendor/league/uri-interfaces/Contracts/HostInterface.php @@ -13,6 +13,9 @@ declare(strict_types=1); namespace League\Uri\Contracts; +/** + * @method string|null encoded() returns RFC3986 encoded host + */ interface HostInterface extends UriComponentInterface { /** diff --git a/vendor/league/uri-interfaces/Contracts/PathInterface.php b/vendor/league/uri-interfaces/Contracts/PathInterface.php index f99b76270..1408eecaf 100644 --- a/vendor/league/uri-interfaces/Contracts/PathInterface.php +++ b/vendor/league/uri-interfaces/Contracts/PathInterface.php @@ -15,6 +15,9 @@ namespace League\Uri\Contracts; use League\Uri\Exceptions\SyntaxError; +/** + * @method static normalize() returns the normalized string representation of the component + */ interface PathInterface extends UriComponentInterface { /** diff --git a/vendor/league/uri-interfaces/Contracts/QueryInterface.php b/vendor/league/uri-interfaces/Contracts/QueryInterface.php index fed486e34..859234521 100644 --- a/vendor/league/uri-interfaces/Contracts/QueryInterface.php +++ b/vendor/league/uri-interfaces/Contracts/QueryInterface.php @@ -28,6 +28,7 @@ use Stringable; * @method bool hasPair(string $key, ?string $value) Tells whether the pair exists in the query. * @method ?string toFormData() Returns the string representation using the applicat/www-form-urlencoded rules * @method ?string toRFC3986() Returns the string representation using RFC3986 rules + * @method self normalize() returns the normalized string representation of the component */ interface QueryInterface extends Countable, IteratorAggregate, UriComponentInterface { diff --git a/vendor/league/uri-interfaces/Contracts/UriAccess.php b/vendor/league/uri-interfaces/Contracts/UriAccess.php index 7c37cdadb..a74da5774 100644 --- a/vendor/league/uri-interfaces/Contracts/UriAccess.php +++ b/vendor/league/uri-interfaces/Contracts/UriAccess.php @@ -15,6 +15,9 @@ namespace League\Uri\Contracts; use Psr\Http\Message\UriInterface as Psr7UriInterface; +/** + * @deprecated since version 7.6.0 + */ interface UriAccess { public function getUri(): UriInterface|Psr7UriInterface; diff --git a/vendor/league/uri-interfaces/Contracts/UriComponentInterface.php b/vendor/league/uri-interfaces/Contracts/UriComponentInterface.php index e478516e8..fa550aaf5 100644 --- a/vendor/league/uri-interfaces/Contracts/UriComponentInterface.php +++ b/vendor/league/uri-interfaces/Contracts/UriComponentInterface.php @@ -16,6 +16,10 @@ namespace League\Uri\Contracts; use JsonSerializable; use Stringable; +/** + * @method static when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null) conditionally return a new instance + * @method bool equals(mixed $value) tells whether the submitted value is equal to the current instance value + */ interface UriComponentInterface extends JsonSerializable, Stringable { /** @@ -36,7 +40,7 @@ interface UriComponentInterface extends JsonSerializable, Stringable * but MUST NOT double-encode any characters. To determine what characters * to encode, please refer to RFC 3986, Sections 2 and 3. * - * If the instance is not defined an empty string is returned + * If the instance is not defined, an empty string is returned */ public function toString(): string; @@ -47,7 +51,7 @@ interface UriComponentInterface extends JsonSerializable, Stringable * but MUST NOT double-encode any characters. To determine what characters * to encode, please refer to RFC 3986, Sections 2 and 3. * - * If the instance is not defined an empty string is returned + * If the instance is not defined, an empty string is returned */ public function __toString(): string; @@ -58,7 +62,7 @@ interface UriComponentInterface extends JsonSerializable, Stringable * but MUST NOT double-encode any characters. To determine what characters * to encode, please refer to RFC 3986 or RFC 1738. * - * If the instance is not defined null is returned + * If the instance is not defined, null is returned */ public function jsonSerialize(): ?string; @@ -69,7 +73,7 @@ interface UriComponentInterface extends JsonSerializable, Stringable * characters. To determine what characters to encode, please refer to RFC 3986, * Sections 2 and 3. * - * If the instance is not defined an empty string is returned + * If the instance is not defined, an empty string is returned */ public function getUriComponent(): string; } diff --git a/vendor/league/uri-interfaces/Contracts/UriInterface.php b/vendor/league/uri-interfaces/Contracts/UriInterface.php index 1fde6b966..60e6c8e75 100644 --- a/vendor/league/uri-interfaces/Contracts/UriInterface.php +++ b/vendor/league/uri-interfaces/Contracts/UriInterface.php @@ -23,8 +23,15 @@ use Stringable; * @phpstan-import-type ComponentMap from UriString * * @method string|null getUsername() returns the user component of the URI. + * @method self withUsername(?string $user) returns a new URI instance with the user component updated. * @method string|null getPassword() returns the scheme-specific information about how to gain authorization to access the resource. + * @method self withPassword(?string $password) returns a new URI instance with the password component updated. + * @method string toAsciiString() returns the string representation of the URI in its RFC3986 form + * @method string toUnicodeString() returns the string representation of the URI in its RFC3987 form (the host is in its IDN form) * @method array toComponents() returns an associative array containing all the URI components. + * @method self normalize() returns a new URI instance with normalized components + * @method self resolve(UriInterface $uri) resolves a URI against a base URI using RFC3986 rules + * @method self relativize(UriInterface $uri) relativize a URI against a base URI using RFC3986 rules */ interface UriInterface extends JsonSerializable, Stringable { diff --git a/vendor/league/uri-interfaces/Encoder.php b/vendor/league/uri-interfaces/Encoder.php index 4324e03c8..5e369a01e 100644 --- a/vendor/league/uri-interfaces/Encoder.php +++ b/vendor/league/uri-interfaces/Encoder.php @@ -14,17 +14,30 @@ declare(strict_types=1); namespace League\Uri; use Closure; +use Deprecated; use League\Uri\Contracts\UriComponentInterface; use League\Uri\Exceptions\SyntaxError; +use League\Uri\IPv6\Converter as IPv6Converter; use SensitiveParameter; use Stringable; +use function explode; +use function filter_var; +use function gettype; +use function in_array; +use function is_scalar; use function preg_match; use function preg_replace_callback; use function rawurldecode; use function rawurlencode; +use function sprintf; +use function str_starts_with; +use function strtolower; use function strtoupper; +use const FILTER_FLAG_IPV4; +use const FILTER_VALIDATE_IP; + final class Encoder { private const REGEXP_CHARS_INVALID = '/[\x00-\x1f\x7f]/'; @@ -41,6 +54,23 @@ final class Encoder private const REGEXP_PART_UNRESERVED = 'A-Za-z\d_\-.~'; private const REGEXP_PART_ENCODED = '%(?![A-Fa-f\d]{2})'; + /** + * Unreserved characters. + * + * @see https://www.rfc-editor.org/rfc/rfc3986.html#section-2.3 + */ + private const REGEXP_UNRESERVED_CHARACTERS = ',%(2[1-9A-Fa-f]|[3-7][0-9A-Fa-f]|61|62|64|65|66|7[AB]|5F),'; + + /** + * Tell whether the user component is correctly encoded. + */ + public static function isUserEncoded(Stringable|string|null $encoded): bool + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.']+|'.self::REGEXP_PART_ENCODED.'/'; + + return null === $encoded || 1 !== preg_match($pattern, (string) $encoded); + } + /** * Encode User. * @@ -53,6 +83,41 @@ final class Encoder return self::encode($component, $pattern); } + /** + * Normalize user component. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986. + */ + public static function normalizeUser(Stringable|string|null $user): ?string + { + return self::normalize(self::encodeUser(self::decodeUnreservedCharacters($user))); + } + + private static function normalize(?string $component): ?string + { + if (null === $component) { + return null; + } + + return (string) preg_replace_callback( + '/%[0-9a-f]{2}/i', + static fn (array $found) => strtoupper($found[0]), + $component + ); + } + + /** + * Tell whether the password component is correctly encoded. + */ + public static function isPasswordEncoded(#[SensitiveParameter] Stringable|string|null $encoded): bool + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':]+|'.self::REGEXP_PART_ENCODED.'/'; + + return null === $encoded || 1 !== preg_match($pattern, (string) $encoded); + } + /** * Encode Password. * @@ -65,6 +130,113 @@ final class Encoder return self::encode($component, $pattern); } + /** + * Normalize password component. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986. + */ + public static function normalizePassword(#[SensitiveParameter] Stringable|string|null $password): ?string + { + return self::normalize(self::encodePassword(self::decodeUnreservedCharacters($password))); + } + + /** + * Tell whether the userInfo component is correctly encoded. + */ + public static function isUserInfoEncoded(#[SensitiveParameter] Stringable|string|null $userInfo): bool + { + if (null === $userInfo) { + return true; + } + + [$user, $password] = explode(':', (string) $userInfo, 2) + [1 => null]; + + return self::isUserEncoded($user) + && self::isPasswordEncoded($password); + } + + public static function encodeUserInfo(#[SensitiveParameter] Stringable|string|null $userInfo): ?string + { + if (null === $userInfo) { + return null; + } + + [$user, $password] = explode(':', (string) $userInfo, 2) + [1 => null]; + $userInfo = self::encodeUser($user); + if (null === $password) { + return $userInfo; + } + + return $userInfo.':'.self::encodePassword($password); + } + + public static function normalizeUserInfo(#[SensitiveParameter] Stringable|string|null $userInfo): ?string + { + if (null === $userInfo) { + return null; + } + + [$user, $password] = explode(':', (string) $userInfo, 2) + [1 => null]; + $userInfo = self::normalizeUser($user); + if (null === $password) { + return $userInfo; + } + + return $userInfo.':'.self::normalizePassword($password); + } + + /** + * Decodes all the URI component characters. + */ + public static function decodeAll(Stringable|string|null $component): ?string + { + return self::decode($component, static fn (array $matches): string => rawurldecode($matches[0])); + } + + /** + * Decodes the URI component without decoding the unreserved characters which are already encoded. + */ + public static function decodeNecessary(Stringable|string|int|null $component): ?string + { + $decoder = static function (array $matches): string { + if (1 === preg_match(self::REGEXP_CHARS_PREVENTS_DECODING, $matches[0])) { + return strtoupper($matches[0]); + } + + return rawurldecode($matches[0]); + }; + + return self::decode($component, $decoder); + } + + /** + * Decodes the component unreserved characters. + */ + public static function decodeUnreservedCharacters(Stringable|string|null $str): ?string + { + if (null === $str) { + return null; + } + + return preg_replace_callback( + self::REGEXP_UNRESERVED_CHARACTERS, + static fn (array $matches): string => rawurldecode($matches[0]), + (string) $str + ); + } + + /** + * Tell whether the path component is correctly encoded. + */ + public static function isPathEncoded(Stringable|string|null $encoded): bool + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/]+|'.self::REGEXP_PART_ENCODED.'/'; + + return null === $encoded || 1 !== preg_match($pattern, (string) $encoded); + } + /** * Encode Path. * @@ -77,6 +249,130 @@ final class Encoder return (string) self::encode($component, $pattern); } + /** + * Decodes the path component while preserving characters that should not be decoded in the context of a full valid URI. + */ + public static function decodePath(Stringable|string|null $path): ?string + { + $decoder = static function (array $matches): string { + $encodedChar = strtoupper($matches[0]); + + return in_array($encodedChar, ['%2F', '%20', '%3F', '%23'], true) ? $encodedChar : rawurldecode($encodedChar); + }; + + return self::decode($path, $decoder); + } + + /** + * Normalize path component. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986. + */ + public static function normalizePath(Stringable|string|null $component): ?string + { + return self::normalize(self::encodePath(self::decodePath($component))); + } + + /** + * Tell whether the query component is correctly encoded. + */ + public static function isQueryEncoded(Stringable|string|null $encoded): bool + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.'\/?%]+|'.self::REGEXP_PART_ENCODED.'/'; + + return null === $encoded || 1 !== preg_match($pattern, (string) $encoded); + } + + /** + * Decodes the query component while preserving characters that should not be decoded in the context of a full valid URI. + */ + public static function decodeQuery(Stringable|string|null $path): ?string + { + $decoder = static function (array $matches): string { + $encodedChar = strtoupper($matches[0]); + + return in_array($encodedChar, ['%26', '%3D', '%20', '%23', '%3F'], true) ? $encodedChar : rawurldecode($encodedChar); + }; + + return self::decode($path, $decoder); + } + + /** + * Normalize the query component. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986. + */ + public static function normalizeQuery(Stringable|string|null $query): ?string + { + return self::normalize(self::encodeQueryOrFragment(self::decodeQuery($query))); + } + + /** + * Tell whether the query component is correctly encoded. + */ + public static function isFragmentEncoded(Stringable|string|null $encoded): bool + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/?%]|'.self::REGEXP_PART_ENCODED.'/'; + + return null === $encoded || 1 !== preg_match($pattern, (string) $encoded); + } + + /** + * Decodes the fragment component while preserving characters that should not be decoded in the context of a full valid URI. + */ + public static function decodeFragment(Stringable|string|null $path): ?string + { + return self::decode($path, static fn (array $matches): string => '%20' === $matches[0] ? $matches[0] : rawurldecode($matches[0])); + } + + /** + * Normalize the fragment component. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986. + */ + public static function normalizeFragment(Stringable|string|null $fragment): ?string + { + return self::normalize(self::encodeQueryOrFragment(self::decodeFragment($fragment))); + } + + /** + * Normalize the host component. + * + * @see https://www.rfc-editor.org/rfc/rfc3986.html#section-3.2.2 + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986. + */ + public static function normalizeHost(Stringable|string|null $host): ?string + { + if ($host instanceof Stringable) { + $host = (string) $host; + } + + if (null === $host || '' === $host || false !== filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return $host; + } + + if (str_starts_with($host, '[')) { + return IPv6Converter::normalize($host); + } + + $host = strtolower($host); + + return (!str_contains($host, '%')) ? $host : preg_replace_callback( + '/%[a-f0-9]{2}/', + fn (array $matches) => 1 === preg_match('/%([0-7][0-9a-f])/', $matches[0]) ? rawurldecode($matches[0]) : strtoupper($matches[0]), + $host + ); + } + /** * Encode Query or Fragment. * @@ -92,45 +388,16 @@ final class Encoder public static function encodeQueryKeyValue(mixed $component): ?string { static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.']+|'.self::REGEXP_PART_ENCODED.'/'; - - $encodeMatches = static fn (array $matches): string => match (1) { - preg_match('/[^'.self::REGEXP_PART_UNRESERVED.']/', rawurldecode($matches[0])) => rawurlencode($matches[0]), - default => $matches[0], - }; - - $component = self::filterComponent($component); + $encoder = static fn (array $found): string => 1 === preg_match('/[^'.self::REGEXP_PART_UNRESERVED.']/', rawurldecode($found[0])) ? rawurlencode($found[0]) : $found[0]; + $filteredComponent = self::filterComponent($component); return match (true) { - !is_scalar($component) => throw new SyntaxError(sprintf('A pair key/value must be a scalar value `%s` given.', gettype($component))), - 1 === preg_match(self::REGEXP_CHARS_INVALID, $component) => rawurlencode($component), - 1 === preg_match($pattern, $component) => (string) preg_replace_callback($pattern, $encodeMatches(...), $component), - default => $component, + null === $filteredComponent => throw new SyntaxError(sprintf('A pair key/value must be a scalar value `%s` given.', gettype($component))), + 1 === preg_match(self::REGEXP_CHARS_INVALID, $filteredComponent) => rawurlencode($filteredComponent), + default => (string) preg_replace_callback($pattern, $encoder, $filteredComponent), }; } - /** - * Decodes the URI component without decoding the unreserved characters which are already encoded. - */ - public static function decodePartial(Stringable|string|int|null $component): ?string - { - $decodeMatches = static fn (array $matches): string => match (1) { - preg_match(self::REGEXP_CHARS_PREVENTS_DECODING, $matches[0]) => strtoupper($matches[0]), - default => rawurldecode($matches[0]), - }; - - return self::decode($component, $decodeMatches); - } - - /** - * Decodes all the URI component characters. - */ - public static function decodeAll(Stringable|string|int|null $component): ?string - { - $decodeMatches = static fn (array $matches): string => rawurldecode($matches[0]); - - return self::decode($component, $decodeMatches); - } - private static function filterComponent(mixed $component): ?string { return match (true) { @@ -144,33 +411,58 @@ final class Encoder }; } + /** + * Encodes the URI component characters using a regular expression to find which characters need encoding. + */ private static function encode(Stringable|string|int|bool|null $component, string $pattern): ?string { $component = self::filterComponent($component); - $encodeMatches = static fn (array $matches): string => match (1) { - preg_match('/[^'.self::REGEXP_PART_UNRESERVED.']/', rawurldecode($matches[0])) => rawurlencode($matches[0]), - default => $matches[0], - }; + if (null === $component || '' === $component) { + return $component; + } - return match (true) { - null === $component, - '' === $component => $component, - default => (string) preg_replace_callback($pattern, $encodeMatches(...), $component), - }; + return (string) preg_replace_callback( + $pattern, + static fn (array $found): string => 1 === preg_match('/[^'.self::REGEXP_PART_UNRESERVED.']/', rawurldecode($found[0])) ? rawurlencode($found[0]) : $found[0], + $component + ); } /** - * Decodes all the URI component characters. + * Decodes the URI component characters using a closure. */ - private static function decode(Stringable|string|int|null $component, Closure $decodeMatches): ?string + private static function decode(Stringable|string|int|null $component, Closure $decoder): ?string { $component = self::filterComponent($component); + if (null === $component || '' === $component) { + return $component; + } - return match (true) { - null === $component => null, - 1 === preg_match(self::REGEXP_CHARS_INVALID, $component) => throw new SyntaxError('Invalid component string: '.$component.'.'), - 1 === preg_match(self::REGEXP_CHARS_ENCODED, $component) => preg_replace_callback(self::REGEXP_CHARS_ENCODED, $decodeMatches(...), $component), - default => $component, - }; + if (1 === preg_match(self::REGEXP_CHARS_INVALID, $component)) { + throw new SyntaxError('Invalid component string: '.$component.'.'); + } + + if (1 === preg_match(self::REGEXP_CHARS_ENCODED, $component)) { + return (string) preg_replace_callback(self::REGEXP_CHARS_ENCODED, $decoder, $component); + } + + return $component; + } + + /** + * Decodes the URI component without decoding the unreserved characters which are already encoded. + * + * DEPRECATION WARNING! This method will be removed in the next major point release. + * + * @deprecated Since version 7.6.0 + * @codeCoverageIgnore + * @see Encoder::decodeNecessary() + * + * Create a new instance from the environment. + */ + #[Deprecated(message:'use League\Uri\Encoder::decodeNecessary() instead', since:'league/uri:7.6.0')] + public static function decodePartial(Stringable|string|int|null $component): ?string + { + return self::decodeNecessary($component); } } diff --git a/vendor/league/uri-interfaces/FeatureDetection.php b/vendor/league/uri-interfaces/FeatureDetection.php index b3e9b09cd..44d659ca4 100644 --- a/vendor/league/uri-interfaces/FeatureDetection.php +++ b/vendor/league/uri-interfaces/FeatureDetection.php @@ -17,6 +17,11 @@ use finfo; use League\Uri\Exceptions\MissingFeature; use League\Uri\IPv4\Calculator; +use function class_exists; +use function defined; +use function extension_loaded; +use function function_exists; + use const PHP_INT_SIZE; /** @@ -29,9 +34,8 @@ final class FeatureDetection static $isSupported = null; $isSupported = $isSupported ?? class_exists(finfo::class); - if (!$isSupported) { - throw new MissingFeature('Support for file type detection requires the `fileinfo` extension.'); - } + $isSupported || throw new MissingFeature('Support for file type detection requires the `fileinfo` extension.'); + } public static function supportsIdn(): void @@ -39,9 +43,8 @@ final class FeatureDetection static $isSupported = null; $isSupported = $isSupported ?? (function_exists('\idn_to_ascii') && defined('\INTL_IDNA_VARIANT_UTS46')); - if (!$isSupported) { - throw new MissingFeature('Support for IDN host requires the `intl` extension for best performance or run "composer require symfony/polyfill-intl-idn" to install a polyfill.'); - } + $isSupported || throw new MissingFeature('Support for IDN host requires the `intl` extension for best performance or run "composer require symfony/polyfill-intl-idn" to install a polyfill.'); + } public static function supportsIPv4Conversion(): void @@ -49,8 +52,14 @@ final class FeatureDetection static $isSupported = null; $isSupported = $isSupported ?? (extension_loaded('gmp') || extension_loaded('bcmath') || (4 < PHP_INT_SIZE)); - if (!$isSupported) { - throw new MissingFeature('A '.Calculator::class.' implementation could not be automatically loaded. To perform IPv4 conversion use a x.64 PHP build or install one of the following extension GMP or BCMath. You can also ship your own implmentation.'); - } + $isSupported || throw new MissingFeature('A '.Calculator::class.' implementation could not be automatically loaded. To perform IPv4 conversion use a x.64 PHP build or install one of the following extension GMP or BCMath. You can also ship your own implementation.'); + } + + public static function supportsDom(): void + { + static $isSupported = null; + $isSupported = $isSupported ?? extension_loaded('dom'); + + $isSupported || throw new MissingFeature('To use a DOM related feature, the DOM extension must be installed in your system.'); } } diff --git a/vendor/league/uri-interfaces/IPv4/BCMathCalculator.php b/vendor/league/uri-interfaces/IPv4/BCMathCalculator.php index b12ac9954..73c5c56e2 100644 --- a/vendor/league/uri-interfaces/IPv4/BCMathCalculator.php +++ b/vendor/league/uri-interfaces/IPv4/BCMathCalculator.php @@ -53,12 +53,12 @@ final class BCMathCalculator implements Calculator return bcpow((string) $value, (string) $exponent, self::SCALE); } - public function compare(mixed $value1, $value2): int + public function compare(mixed $value1, mixed $value2): int { return bccomp((string) $value1, (string) $value2, self::SCALE); } - public function multiply(mixed $value1, $value2): string + public function multiply(mixed $value1, mixed $value2): string { return bcmul((string) $value1, (string) $value2, self::SCALE); } diff --git a/vendor/league/uri-interfaces/IPv4/Converter.php b/vendor/league/uri-interfaces/IPv4/Converter.php index 71c0bb9fc..2434ea115 100644 --- a/vendor/league/uri-interfaces/IPv4/Converter.php +++ b/vendor/league/uri-interfaces/IPv4/Converter.php @@ -21,6 +21,8 @@ use function array_pop; use function count; use function explode; use function extension_loaded; +use function hexdec; +use function long2ip; use function ltrim; use function preg_match; use function str_ends_with; @@ -125,9 +127,13 @@ final class Converter } $hexParts = explode(':', substr($ipAddress, 5, 9)); + if (count($hexParts) < 2) { + return false; + } - return count($hexParts) > 1 - && false !== long2ip((int) hexdec($hexParts[0]) * 65536 + (int) hexdec($hexParts[1])); + $ipAddress = long2ip((int) hexdec($hexParts[0]) * 65536 + (int) hexdec($hexParts[1])); + + return '' !== ''.$ipAddress; } public function toIPv6Using6to4(Stringable|string|null $host): ?string diff --git a/vendor/league/uri-interfaces/IPv6/Converter.php b/vendor/league/uri-interfaces/IPv6/Converter.php index f645c1da2..8f42c592f 100644 --- a/vendor/league/uri-interfaces/IPv6/Converter.php +++ b/vendor/league/uri-interfaces/IPv6/Converter.php @@ -84,7 +84,7 @@ final class Converter return self::build($components); } - private static function build(array $components): string + public static function build(array $components): string { $components['ipAddress'] ??= null; $components['zoneIdentifier'] ??= null; @@ -99,9 +99,7 @@ final class Converter }.']'; } - /**] - * @param Stringable|string|null $host - * + /** * @return array{ipAddress:string|null, zoneIdentifier:string|null} */ private static function parse(Stringable|string|null $host): array @@ -134,4 +132,29 @@ final class Converter default => ['ipAddress' => null, 'zoneIdentifier' => null], }; } + + /** + * Tells whether the host is an IPv6. + */ + public static function isIpv6(Stringable|string|null $host): bool + { + return null !== self::parse($host)['ipAddress']; + } + + public static function normalize(Stringable|string|null $host): ?string + { + if (null === $host || '' === $host) { + return $host; + } + + $host = (string) $host; + $components = self::parse($host); + if (null === $components['ipAddress']) { + return strtolower($host); + } + + $components['ipAddress'] = strtolower($components['ipAddress']); + + return self::build($components); + } } diff --git a/vendor/league/uri-interfaces/Idna/Converter.php b/vendor/league/uri-interfaces/Idna/Converter.php index b993e9e09..f4bd4857c 100644 --- a/vendor/league/uri-interfaces/Idna/Converter.php +++ b/vendor/league/uri-interfaces/Idna/Converter.php @@ -21,6 +21,7 @@ use Stringable; use function idn_to_ascii; use function idn_to_utf8; use function rawurldecode; +use function strtolower; use const INTL_IDNA_VARIANT_UTS46; @@ -141,7 +142,7 @@ final class Converter $domain = rawurldecode((string) $domain); if (false === stripos($domain, 'xn--')) { - return Result::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => Error::NONE->value]); + return Result::fromIntl(['result' => strtolower($domain), 'isTransitionalDifferent' => false, 'errors' => Error::NONE->value]); } FeatureDetection::supportsIdn(); @@ -155,7 +156,7 @@ final class Converter idn_to_utf8($domain, $flags->toBytes(), INTL_IDNA_VARIANT_UTS46, $idnaInfo); if ([] === $idnaInfo) { - return Result::fromIntl(['result' => $domain, 'isTransitionalDifferent' => false, 'errors' => Error::NONE->value]); + return Result::fromIntl(['result' => strtolower($domain), 'isTransitionalDifferent' => false, 'errors' => Error::NONE->value]); } return Result::fromIntl($idnaInfo); @@ -164,7 +165,7 @@ final class Converter /** * Tells whether the submitted host is a valid IDN regardless of its format. * - * Returns false if the host is invalid or if its conversion yield the same result + * Returns false if the host is invalid or if its conversion yields the same result */ public static function isIdn(Stringable|string|null $domain): bool { diff --git a/vendor/league/uri-interfaces/UriComparisonMode.php b/vendor/league/uri-interfaces/UriComparisonMode.php new file mode 100644 index 000000000..f06f703e5 --- /dev/null +++ b/vendor/league/uri-interfaces/UriComparisonMode.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri; + +enum UriComparisonMode +{ + case IncludeFragment; + case ExcludeFragment; +} diff --git a/vendor/league/uri-interfaces/UriString.php b/vendor/league/uri-interfaces/UriString.php index a79184982..413c994a2 100644 --- a/vendor/league/uri-interfaces/UriString.php +++ b/vendor/league/uri-interfaces/UriString.php @@ -16,19 +16,30 @@ namespace League\Uri; use League\Uri\Exceptions\ConversionFailed; use League\Uri\Exceptions\MissingFeature; use League\Uri\Exceptions\SyntaxError; -use League\Uri\Idna\Converter; +use League\Uri\Idna\Converter as IdnaConverter; use Stringable; +use Throwable; +use function array_map; use function array_merge; +use function array_pop; +use function array_reduce; +use function defined; use function explode; use function filter_var; +use function function_exists; +use function implode; +use function in_array; use function inet_pton; use function preg_match; use function rawurldecode; use function sprintf; +use function str_replace; use function strpos; +use function strtolower; use function substr; +use const FILTER_FLAG_IPV4; use const FILTER_FLAG_IPV6; use const FILTER_VALIDATE_IP; @@ -40,8 +51,8 @@ use const FILTER_VALIDATE_IP; * @author Ignace Nyamagana Butera * @since 6.0.0 * - * @phpstan-type AuthorityMap array{user:?string, pass:?string, host:?string, port:?int} - * @phpstan-type ComponentMap array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} + * @phpstan-type AuthorityMap array{user: ?string, pass: ?string, host: ?string, port: ?int} + * @phpstan-type ComponentMap array{scheme: ?string, user: ?string, pass: ?string, host: ?string, port: ?int, path: string, query: ?string, fragment: ?string} * @phpstan-type InputComponentMap array{scheme? : ?string, user? : ?string, pass? : ?string, host? : ?string, port? : ?int, path? : ?string, query? : ?string, fragment? : ?string} */ final class UriString @@ -62,20 +73,28 @@ final class UriString * @var array> */ private const URI_SHORTCUTS = [ - '' => [], + '' => ['path' => ''], '#' => ['fragment' => ''], '?' => ['query' => ''], '?#' => ['query' => '', 'fragment' => ''], '/' => ['path' => '/'], '//' => ['host' => ''], + '///' => ['host' => '', 'path' => '/'], ]; /** - * Range of invalid characters in URI string. + * Range of invalid characters in URI 3986 string. * * @var string */ - private const REGEXP_INVALID_URI_CHARS = '/[\x00-\x1f\x7f]/'; + private const REGEXP_VALID_URI_RFC3986_CHARS = '/^(?:[A-Za-z0-9\-._~:\/?#[\]@!$&\'()*+,;=%]|%[0-9A-Fa-f]{2})*$/'; + + /** + * Range of invalid characters in URI 3987 string. + * + * @var string + */ + private const REGEXP_INVALID_URI_RFC3987_CHARS = '/[\x00-\x1f\x7f\s]/'; /** * RFC3986 regular expression URI splitter. @@ -159,6 +178,9 @@ final class UriString */ private const REGEXP_IDN_PATTERN = '/[^\x20-\x7f]/'; + /** @var array */ + private const DOT_SEGMENTS = ['.' => 1, '..' => 1]; + /** * Only the address block fe80::/10 can have a Zone ID attach to * let's detect the link local significant 10 bits. @@ -174,6 +196,45 @@ final class UriString */ private const MAXIMUM_HOST_CACHED = 100; + /** + * Generate an IRI string representation (RFC3987) from its parsed representation + * returned by League\UriString::parse() or PHP's parse_url. + * + * If you supply your own array, you are responsible for providing + * valid components without their URI delimiters. + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + * @link https://tools.ietf.org/html/rfc3986#section-7.5 + */ + public static function toIriString(Stringable|string $uri): string + { + $components = UriString::parse($uri); + $port = null; + if (isset($components['port'])) { + $port = (int) $components['port']; + unset($components['port']); + } + + if (null !== $components['host']) { + $components['host'] = IdnaConverter::toUnicode($components['host'])->domain(); + } + + $components['path'] = Encoder::decodePath($components['path']); + $components['user'] = Encoder::decodeNecessary($components['user']); + $components['pass'] = Encoder::decodeNecessary($components['pass']); + $components['query'] = Encoder::decodeQuery($components['query']); + $components['fragment'] = Encoder::decodeFragment($components['fragment']); + + return self::build([ + ...array_map(fn (?string $value) => match (true) { + null === $value, + !str_contains($value, '%20') => $value, + default => str_replace('%20', ' ', $value), + }, $components), + ...['port' => $port], + ]); + } + /** * Generate a URI string representation from its parsed representation * returned by League\UriString::parse() or PHP's parse_url. @@ -191,28 +252,29 @@ final class UriString return self::buildUri( $components['scheme'] ?? null, self::buildAuthority($components), - $components['path'] ?? '', + $components['path'] ?? null, $components['query'] ?? null, $components['fragment'] ?? null, ); } /** - * Generate a URI string representation based on RFC3986 algorithm. + * Generates a URI string representation based on RFC3986 algorithm. * - * valid URI component MUST be provided without their URI delimiters + * Valid URI component MUST be provided without their URI delimiters * but properly encoded. * * @link https://tools.ietf.org/html/rfc3986#section-5.3 - * @link https://tools.ietf.org/html/rfc3986#section-7.5 + * @link https://tools.ietf.org/html/rfc3986#section-7.5§ */ public static function buildUri( - ?string $scheme, - ?string $authority, - string $path, - ?string $query, - ?string $fragment, + ?string $scheme = null, + ?string $authority = null, + ?string $path = null, + ?string $query = null, + ?string $fragment = null, ): string { + self::validateComponents($scheme, $authority, $path); $uri = ''; if (null !== $scheme) { $uri .= $scheme.':'; @@ -245,21 +307,227 @@ final class UriString return null; } - $authority = $components['host']; + $userInfo = $components['user'] ?? null; + if (isset($components['pass'])) { + $userInfo .= ':'.$components['pass']; + } + + $authority = ''; + if (isset($userInfo)) { + $authority .= $userInfo.'@'; + } + + $authority .= $components['host']; if (isset($components['port'])) { $authority .= ':'.$components['port']; } - if (!isset($components['user'])) { - return $authority; + return $authority; + } + + /** + * Parses and normalizes the URI following RFC3986 destructive and non-destructive constraints. + * + * @throws SyntaxError if the URI is not parsable + * + * @return ComponentMap + */ + public static function parseNormalized(Stringable|string $uri): array + { + $components = self::parse($uri); + if (null !== $components['scheme']) { + $components['scheme'] = strtolower($components['scheme']); } - $authority = '@'.$authority; - if (!isset($components['pass'])) { - return $components['user'].$authority; + $components['host'] = self::normalizeHost($components['host']); + $path = $components['path']; + $authority = self::buildAuthority($components); + //dot segment only happens when: + // - the path is absolute + // - the scheme and/or the authority are defined + if ('/' === ($path[0] ?? '') || '' !== $components['scheme'].$authority) { + $path = self::removeDotSegments($path); } - return $components['user'].':'.$components['pass'].$authority; + // if there is an authority, the path must be absolute + if ('' !== $path && '/' !== $path[0]) { + if (null !== $authority) { + $path = '/'.$path; + } + } + + $components['path'] = (string) Encoder::normalizePath($path); + $components['query'] = Encoder::normalizeQuery($components['query']); + $components['fragment'] = Encoder::normalizeFragment($components['fragment']); + $components['user'] = Encoder::normalizeUser($components['user']); + $components['pass'] = Encoder::normalizePassword($components['pass']); + + return $components; + } + + /** + * Parses and normalizes the URI following RFC3986 destructive and non-destructive constraints. + * + * @throws SyntaxError if the URI is not parsable + */ + public static function normalize(Stringable|string $uri): string + { + return self::build(self::parseNormalized($uri)); + } + + /** + * Parses and normalizes the URI following RFC3986 destructive and non-destructive constraints. + * + * @throws SyntaxError if the URI is not parsable + */ + public static function normalizeAuthority(Stringable|string|null $authority): ?string + { + if (null === $authority) { + return null; + } + + $components = UriString::parseAuthority($authority); + $components['host'] = self::normalizeHost($components['host'] ?? null); + $components['user'] = Encoder::normalizeUser($components['user']); + $components['pass'] = Encoder::normalizePassword($components['pass']); + + return (string) self::buildAuthority($components); + } + + /** + * Resolves a URI against a base URI using RFC3986 rules. + * + * This method MUST retain the state of the submitted URI instance, and return + * a URI instance of the same type that contains the applied modifications. + * + * This method MUST be transparent when dealing with error and exceptions. + * It MUST not alter or silence them apart from validating its own parameters. + * + * @see https://www.rfc-editor.org/rfc/rfc3986.html#section-5 + * + * @throws SyntaxError if the BaseUri is not absolute or in absence of a BaseUri if the uri is not absolute + */ + public static function resolve(Stringable|string $uri, Stringable|string|null $baseUri = null): string + { + $uri = (string) $uri; + if ('' === $uri) { + $uri = $baseUri ?? throw new SyntaxError('The uri can not be the empty string when there\'s no base URI.'); + } + + $uriComponents = self::parse($uri); + $baseUriComponents = $uriComponents; + if (null !== $baseUri && (string) $uri !== (string) $baseUri) { + $baseUriComponents = self::parse($baseUri); + } + + $hasLeadingSlash = str_starts_with($baseUriComponents['path'], '/'); + if (null === $baseUriComponents['scheme']) { + throw new SyntaxError('The base URI must be an absolute URI or null; If the base URI is null the URI must be an absolute URI.'); + } + + if (null !== $uriComponents['scheme'] && '' !== $uriComponents['scheme']) { + $uriComponents['path'] = self::removeDotSegments($uriComponents['path']); + + return UriString::build($uriComponents); + } + + if (null !== self::buildAuthority($uriComponents)) { + $uriComponents['scheme'] = $baseUriComponents['scheme']; + $uriComponents['path'] = self::removeDotSegments($uriComponents['path']); + + return UriString::build($uriComponents); + } + + [$path, $query] = self::resolvePathAndQuery($uriComponents, $baseUriComponents); + $path = UriString::removeDotSegments($path); + if ('' !== $path && '/' !== $path[0] && $hasLeadingSlash) { + $path = '/'.$path; + } + + $baseUriComponents['path'] = $path; + $baseUriComponents['query'] = $query; + $baseUriComponents['fragment'] = $uriComponents['fragment']; + + return UriString::build($baseUriComponents); + } + + /** + * Filter Dot segment according to RFC3986. + * + * @see http://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + public static function removeDotSegments(Stringable|string $path): string + { + $path = (string) $path; + if (!str_contains($path, '.')) { + return $path; + } + + $reducer = function (array $carry, string $segment): array { + if ('..' === $segment) { + array_pop($carry); + + return $carry; + } + + if (!isset(self::DOT_SEGMENTS[$segment])) { + $carry[] = $segment; + } + + return $carry; + }; + + $oldSegments = explode('/', $path); + $newPath = implode('/', array_reduce($oldSegments, $reducer(...), [])); + if (isset(self::DOT_SEGMENTS[$oldSegments[array_key_last($oldSegments)]])) { + $newPath .= '/'; + } + + return $newPath; + } + + /** + * Resolves an URI path and query component. + * + * @param ComponentMap $uri + * @param ComponentMap $baseUri + * + * @return array{0:string, 1:string|null} + */ + private static function resolvePathAndQuery(array $uri, array $baseUri): array + { + if (str_starts_with($uri['path'], '/')) { + return [$uri['path'], $uri['query']]; + } + + if ('' === $uri['path']) { + return [$baseUri['path'], $uri['query'] ?? $baseUri['query']]; + } + + $targetPath = $uri['path']; + if (null !== self::buildAuthority($baseUri) && '' === $baseUri['path']) { + $targetPath = '/'.$targetPath; + } + + if ('' !== $baseUri['path']) { + $segments = explode('/', $baseUri['path']); + array_pop($segments); + if ([] !== $segments) { + $targetPath = implode('/', $segments).'/'.$targetPath; + } + } + + return [$targetPath, $uri['query']]; + } + + public static function containsRfc3986Chars(Stringable|string $uri): bool + { + return 1 === preg_match(self::REGEXP_VALID_URI_RFC3986_CHARS, (string) $uri); + } + + public static function containsRfc3987Chars(Stringable|string $uri): bool + { + return 1 !== preg_match(self::REGEXP_INVALID_URI_RFC3987_CHARS, (string) $uri); } /** @@ -309,16 +577,14 @@ final class UriString $uri = (string) $uri; if (isset(self::URI_SHORTCUTS[$uri])) { /** @var ComponentMap $components */ - $components = array_merge(self::URI_COMPONENTS, self::URI_SHORTCUTS[$uri]); + $components = [...self::URI_COMPONENTS, ...self::URI_SHORTCUTS[$uri]]; return $components; } - if (1 === preg_match(self::REGEXP_INVALID_URI_CHARS, $uri)) { - throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri)); - } + self::containsRfc3987Chars($uri) || throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri)); - //if the first character is a known URI delimiter parsing can be simplified + //if the first character is a known URI delimiter, parsing can be simplified $first_char = $uri[0]; //The URI is made of the fragment only @@ -368,6 +634,41 @@ final class UriString return $components; } + /** + * Assert the URI internal state is valid. + * + * @link https://tools.ietf.org/html/rfc3986#section-3 + * @link https://tools.ietf.org/html/rfc3986#section-3.3 + * + * @throws SyntaxError + */ + private static function validateComponents(?string $scheme, ?string $authority, ?string $path): void + { + if (null !== $authority) { + if (null !== $path && '' !== $path && '/' !== $path[0]) { + throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.'); + } + + return; + } + + if (null === $path || '' === $path) { + return; + } + + if (str_starts_with($path, '//')) { + throw new SyntaxError('If there is no authority the path `'.$path.'` cannot start with a `//`.'); + } + + if (null !== $scheme || false === ($pos = strpos($path, ':'))) { + return; + } + + if (!str_contains(substr($path, 0, $pos), '/')) { + throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.'); + } + } + /** * Parses the URI authority part. * @@ -427,9 +728,13 @@ final class UriString * * @throws SyntaxError if the registered name is invalid */ - private static function filterHost(string $host): string + private static function filterHost(Stringable|string|null $host): ?string { - if ('' === $host) { + if (null !== $host) { + $host = (string) $host; + } + + if (null === $host || '' === $host) { return $host; } @@ -459,18 +764,47 @@ final class UriString throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host)); } + /** + * Tells whether the scheme component is valid. + */ + public static function isValidScheme(Stringable|string|null $scheme): bool + { + return null === $scheme || 1 === preg_match('/^[A-Za-z]([-A-Za-z\d+.]+)?$/', (string) $scheme); + } + + /** + * Tells whether the host component is valid. + */ + public static function isValidHost(Stringable|string|null $host): bool + { + try { + self::filterHost($host); + return true; + } catch (Throwable) { + return false; + } + } + /** * Throws if the host is not a registered name and not a valid IDN host. * * @link https://tools.ietf.org/html/rfc3986#section-3.2.2 * * @throws SyntaxError if the registered name is invalid - * @throws MissingFeature if IDN support or ICU requirement are not available or met. + * @throws MissingFeature if IDN support or ICU requirement, are not available or met. * @throws ConversionFailed if the submitted IDN host cannot be converted to a valid ascii form */ private static function filterRegisteredName(string $host): void { $formattedHost = rawurldecode($host); + if ($formattedHost !== $host) { + if (IdnaConverter::toAscii($formattedHost)->hasErrors()) { + throw new SyntaxError(sprintf('Host `%s` is invalid: the host is not a valid registered name', $host)); + } + + return; + } + if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formattedHost)) { return; } @@ -480,7 +814,7 @@ final class UriString throw new SyntaxError(sprintf('Host `%s` is invalid: the host is not a valid registered name', $host)); } - Converter::toAsciiOrFail($host); + IdnaConverter::toAsciiOrFail($host); } /** @@ -510,4 +844,25 @@ final class UriString return false !== filter_var($ipHost, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && str_starts_with((string)inet_pton($ipHost), self::ZONE_ID_ADDRESS_BLOCK); } + + private static function normalizeHost(?string $host): ?string + { + if (null === $host || false !== filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return $host; + } + + $host = (string) Encoder::normalizeHost($host); + static $isSupported = null; + $isSupported ??= (function_exists('\idn_to_ascii') && defined('\INTL_IDNA_VARIANT_UTS46')); + if (! $isSupported) { + return $host; + } + + $idnaHost = IdnaConverter::toAscii($host); + if (!$idnaHost->hasErrors()) { + return $idnaHost->domain(); + } + + return $host; + } } diff --git a/vendor/league/uri-interfaces/UrnComparisonMode.php b/vendor/league/uri-interfaces/UrnComparisonMode.php new file mode 100644 index 000000000..e742099dd --- /dev/null +++ b/vendor/league/uri-interfaces/UrnComparisonMode.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri; + +enum UrnComparisonMode +{ + case IncludeComponents; + case ExcludeComponents; +} diff --git a/vendor/league/uri-interfaces/composer.json b/vendor/league/uri-interfaces/composer.json index 1e36a98a2..29963848d 100644 --- a/vendor/league/uri-interfaces/composer.json +++ b/vendor/league/uri-interfaces/composer.json @@ -1,7 +1,7 @@ { "name": "league/uri-interfaces", "type": "library", - "description" : "Common interfaces and classes for URI representation and interaction", + "description" : "Common tools for parsing and resolving RFC3987/RFC3986 URI", "keywords": [ "url", "uri", @@ -39,8 +39,7 @@ "require": { "php" : "^8.1", "ext-filter": "*", - "psr/http-message": "^1.1 || ^2.0", - "psr/http-factory": "^1" + "psr/http-message": "^1.1 || ^2.0" }, "autoload": { "psr-4": { @@ -52,7 +51,8 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present", + "rowbot/url": "to handle WHATWG URL" }, "extra": { "branch-alias": { diff --git a/vendor/league/uri/BaseUri.php b/vendor/league/uri/BaseUri.php index c29dc2799..2c28a6be7 100644 --- a/vendor/league/uri/BaseUri.php +++ b/vendor/league/uri/BaseUri.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace League\Uri; +use Deprecated; use JsonSerializable; use League\Uri\Contracts\UriAccess; use League\Uri\Contracts\UriInterface; @@ -27,7 +28,6 @@ use Stringable; use function array_pop; use function array_reduce; use function count; -use function end; use function explode; use function implode; use function in_array; @@ -40,6 +40,10 @@ use function substr; /** * @phpstan-import-type ComponentMap from UriInterface + * @deprecated since version 7.6.0 + * + * @see Modifier + * @see Uri */ class BaseUri implements Stringable, JsonSerializable, UriAccess { @@ -65,7 +69,8 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess public static function from(Stringable|string $uri, ?UriFactoryInterface $uriFactory = null): static { - return new static(static::formatHost(static::filterUri($uri, $uriFactory)), $uriFactory); + $uri = static::formatHost(static::filterUri($uri, $uriFactory)); + return new static($uri, $uriFactory); } public function withUriFactory(UriFactoryInterface $uriFactory): static @@ -189,7 +194,7 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess * Tells whether the URI is opaque or not. * * A URI is opaque if and only if it is absolute - * and does not has an authority path. + * and does not have an authority path. */ public function isOpaque(): bool { @@ -258,7 +263,21 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess */ public function isSameDocument(Stringable|string $uri): bool { - return $this->normalize(static::filterUri($uri)) === $this->normalize($this->uri); + return self::normalizedUri($this->uri)->isSameDocument(self::normalizedUri($uri)); + } + + private static function normalizedUri(Stringable|string $uri): Uri + { + $uri = ($uri instanceof Uri) ? $uri : Uri::new($uri); + $host = $uri->getHost(); + if (null === $host || Ipv4Converter::fromEnvironment()->isIpv4($host) || IPv6Converter::isIpv6($host)) { + return $uri; + } + + /** @var Uri $uri */ + $uri = $uri->withHost(IdnaConverter::toUnicode((string) Ipv6Converter::compress($host))->domain()); + + return $uri; } /** @@ -288,44 +307,12 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess */ public function resolve(Stringable|string $uri): static { - $uri = static::formatHost(static::filterUri($uri, $this->uriFactory)); - $null = $uri instanceof Psr7UriInterface ? '' : null; + $resolved = UriString::resolve($uri, $this->uri->__toString()); - if ($null !== $uri->getScheme()) { - return new static( - $uri->withPath(static::removeDotSegments($uri->getPath())), - $this->uriFactory - ); - } - - if ($null !== $uri->getAuthority()) { - return new static( - $uri - ->withScheme($this->uri->getScheme()) - ->withPath(static::removeDotSegments($uri->getPath())), - $this->uriFactory - ); - } - - $user = $null; - $pass = null; - $userInfo = $this->uri->getUserInfo(); - if (null !== $userInfo) { - [$user, $pass] = explode(':', $userInfo, 2) + [1 => null]; - } - - [$path, $query] = $this->resolvePathAndQuery($uri); - - return new static( - $uri - ->withPath($this->removeDotSegments($path)) - ->withQuery($query) - ->withHost($this->uri->getHost()) - ->withPort($this->uri->getPort()) - ->withUserInfo($user, $pass) - ->withScheme($this->uri->getScheme()), - $this->uriFactory - ); + return new static(match ($this->uriFactory) { + null => Uri::new($resolved), + default => $this->uriFactory->createUri($resolved), + }, $this->uriFactory); } /** @@ -362,71 +349,31 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess final protected function computeOrigin(Psr7UriInterface|UriInterface $uri, ?string $nullValue): Psr7UriInterface|UriInterface|null { - $scheme = $uri->getScheme(); - if ('blob' !== $scheme) { - return match (true) { - isset(static::WHATWG_SPECIAL_SCHEMES[$scheme]) => $uri - ->withFragment($nullValue) - ->withQuery($nullValue) - ->withPath('') - ->withUserInfo($nullValue), - default => null, - }; + if ($uri instanceof Uri) { + $origin = $uri->getOrigin(); + if (null === $origin) { + return null; + } + + return Uri::tryNew($origin); } - $components = UriString::parse($uri->getPath()); - if ($uri instanceof Psr7UriInterface) { - /** @var ComponentMap $components */ - $components = array_map(fn ($component) => null === $component ? '' : $component, $components); + $origin = Uri::tryNew($uri)?->getOrigin(); + if (null === $origin) { + return null; } - return match (true) { - null !== $components['scheme'] && isset(static::WHATWG_SPECIAL_SCHEMES[strtolower($components['scheme'])]) => $uri + $components = UriString::parse($origin); + + return $uri ->withFragment($nullValue) ->withQuery($nullValue) ->withPath('') - ->withHost($components['host']) + ->withScheme('localhost') + ->withHost((string) $components['host']) ->withPort($components['port']) - ->withScheme($components['scheme']) - ->withUserInfo($nullValue), - default => null, - }; - } - - /** - * Normalizes a URI for comparison; this URI string representation is not suitable for usage as per RFC guidelines. - */ - final protected function normalize(Psr7UriInterface|UriInterface $uri): string - { - $null = $uri instanceof Psr7UriInterface ? '' : null; - - $path = $uri->getPath(); - if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) { - $path = $this->removeDotSegments($path); - } - - $query = $uri->getQuery(); - $pairs = null === $query ? [] : explode('&', $query); - sort($pairs); - - static $regexpEncodedChars = ',%(2[D|E]|3\d|4[1-9|A-F]|5[\d|AF]|6[1-9|A-F]|7[\d|E]),i'; - $value = preg_replace_callback( - $regexpEncodedChars, - static fn (array $matches): string => rawurldecode($matches[0]), - [$path, implode('&', $pairs)] - ) ?? ['', $null]; - - [$path, $query] = $value + ['', $null]; - if ($null !== $uri->getAuthority() && '' === $path) { - $path = '/'; - } - - return $uri - ->withHost(Uri::fromComponents(['host' => $uri->getHost()])->getHost()) - ->withPath($path) - ->withQuery([] === $pairs ? $null : $query) - ->withFragment($null) - ->__toString(); + ->withScheme((string) $components['scheme']) + ->withUserInfo($nullValue); } /** @@ -443,92 +390,6 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess }; } - /** - * Remove dot segments from the URI path as per RFC specification. - */ - final protected function removeDotSegments(string $path): string - { - if (!str_contains($path, '.')) { - return $path; - } - - $reducer = function (array $carry, string $segment): array { - if ('..' === $segment) { - array_pop($carry); - - return $carry; - } - - if (!isset(static::DOT_SEGMENTS[$segment])) { - $carry[] = $segment; - } - - return $carry; - }; - - $oldSegments = explode('/', $path); - $newPath = implode('/', array_reduce($oldSegments, $reducer(...), [])); - if (isset(static::DOT_SEGMENTS[end($oldSegments)])) { - $newPath .= '/'; - } - - // @codeCoverageIgnoreStart - // added because some PSR-7 implementations do not respect RFC3986 - if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) { - return '/'.$newPath; - } - // @codeCoverageIgnoreEnd - - return $newPath; - } - - /** - * Resolves an URI path and query component. - * - * @return array{0:string, 1:string|null} - */ - final protected function resolvePathAndQuery(Psr7UriInterface|UriInterface $uri): array - { - $targetPath = $uri->getPath(); - $null = $uri instanceof Psr7UriInterface ? '' : null; - - if (str_starts_with($targetPath, '/')) { - return [$targetPath, $uri->getQuery()]; - } - - if ('' === $targetPath) { - $targetQuery = $uri->getQuery(); - if ($null === $targetQuery) { - $targetQuery = $this->uri->getQuery(); - } - - $targetPath = $this->uri->getPath(); - //@codeCoverageIgnoreStart - //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction - if (null !== $this->uri->getAuthority() && !str_starts_with($targetPath, '/')) { - $targetPath = '/'.$targetPath; - } - //@codeCoverageIgnoreEnd - - return [$targetPath, $targetQuery]; - } - - $basePath = $this->uri->getPath(); - if (null !== $this->uri->getAuthority() && '' === $basePath) { - $targetPath = '/'.$targetPath; - } - - if ('' !== $basePath) { - $segments = explode('/', $basePath); - array_pop($segments); - if ([] !== $segments) { - $targetPath = implode('/', $segments).'/'.$targetPath; - } - } - - return [$targetPath, $uri->getQuery()]; - } - /** * Tells whether the component value from both URI object equals. * @@ -650,9 +511,123 @@ class BaseUri implements Stringable, JsonSerializable, UriAccess final protected static function formatPathWithEmptyBaseQuery(string $path): string { $targetSegments = static::getSegments($path); - /** @var string $basename */ - $basename = end($targetSegments); + $basename = $targetSegments[array_key_last($targetSegments)]; return '' === $basename ? './' : $basename; } + + /** + * Normalizes a URI for comparison; this URI string representation is not suitable for usage as per RFC guidelines. + * + * @deprecated since version 7.6.0 + * + * @codeCoverageIgnore + */ + #[Deprecated(message:'no longer used by the isSameDocument method', since:'league/uri-interfaces:7.6.0')] + final protected function normalize(Psr7UriInterface|UriInterface $uri): string + { + $newUri = $uri->withScheme($uri instanceof Psr7UriInterface ? '' : null); + if ('' === $newUri->__toString()) { + return ''; + } + + return UriString::normalize($newUri); + } + + + /** + * Remove dot segments from the URI path as per RFC specification. + * + * @deprecated since version 7.6.0 + * + * @codeCoverageIgnore + */ + #[Deprecated(message:'no longer used by the isSameDocument method', since:'league/uri-interfaces:7.6.0')] + final protected function removeDotSegments(string $path): string + { + if (!str_contains($path, '.')) { + return $path; + } + + $reducer = function (array $carry, string $segment): array { + if ('..' === $segment) { + array_pop($carry); + + return $carry; + } + + if (!isset(static::DOT_SEGMENTS[$segment])) { + $carry[] = $segment; + } + + return $carry; + }; + + $oldSegments = explode('/', $path); + $newPath = implode('/', array_reduce($oldSegments, $reducer(...), [])); + if (isset(static::DOT_SEGMENTS[$oldSegments[array_key_last($oldSegments)]])) { + $newPath .= '/'; + } + + // @codeCoverageIgnoreStart + // added because some PSR-7 implementations do not respect RFC3986 + if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) { + return '/'.$newPath; + } + // @codeCoverageIgnoreEnd + + return $newPath; + } + + /** + * Resolves an URI path and query component. + * + * @return array{0:string, 1:string|null} + * + * @deprecated since version 7.6.0 + * + * @codeCoverageIgnore + */ + #[Deprecated(message:'no longer used by the isSameDocument method', since:'league/uri-interfaces:7.6.0')] + final protected function resolvePathAndQuery(Psr7UriInterface|UriInterface $uri): array + { + $targetPath = $uri->getPath(); + $null = $uri instanceof Psr7UriInterface ? '' : null; + + if (str_starts_with($targetPath, '/')) { + return [$targetPath, $uri->getQuery()]; + } + + if ('' === $targetPath) { + $targetQuery = $uri->getQuery(); + if ($null === $targetQuery) { + $targetQuery = $this->uri->getQuery(); + } + + $targetPath = $this->uri->getPath(); + //@codeCoverageIgnoreStart + //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction + if (null !== $this->uri->getAuthority() && !str_starts_with($targetPath, '/')) { + $targetPath = '/'.$targetPath; + } + //@codeCoverageIgnoreEnd + + return [$targetPath, $targetQuery]; + } + + $basePath = $this->uri->getPath(); + if (null !== $this->uri->getAuthority() && '' === $basePath) { + $targetPath = '/'.$targetPath; + } + + if ('' !== $basePath) { + $segments = explode('/', $basePath); + array_pop($segments); + if ([] !== $segments) { + $targetPath = implode('/', $segments).'/'.$targetPath; + } + } + + return [$targetPath, $uri->getQuery()]; + } } diff --git a/vendor/league/uri/Http.php b/vendor/league/uri/Http.php index 0293a9864..7bb412015 100644 --- a/vendor/league/uri/Http.php +++ b/vendor/league/uri/Http.php @@ -15,17 +15,23 @@ namespace League\Uri; use Deprecated; use JsonSerializable; +use League\Uri\Contracts\Conditionable; use League\Uri\Contracts\UriException; use League\Uri\Contracts\UriInterface; use League\Uri\Exceptions\SyntaxError; use League\Uri\UriTemplate\TemplateCanNotBeExpanded; use Psr\Http\Message\UriInterface as Psr7UriInterface; use Stringable; +use Uri\Rfc3986\Uri as Rfc3986Uri; +use Uri\WhatWg\Url as WhatWgUrl; + +use function is_bool; +use function ltrim; /** * @phpstan-import-type InputComponentMap from UriString */ -final class Http implements Stringable, Psr7UriInterface, JsonSerializable +final class Http implements Stringable, Psr7UriInterface, JsonSerializable, Conditionable { private readonly UriInterface $uri; @@ -75,9 +81,21 @@ final class Http implements Stringable, Psr7UriInterface, JsonSerializable /** * Create a new instance from a string or a stringable object. */ - public static function new(Stringable|string $uri = ''): self + public static function new(Rfc3986Uri|WhatwgUrl|Stringable|string $uri = ''): self { - return self::fromComponents(UriString::parse($uri)); + return new self(Uri::new($uri)); + } + + /** + * Create a new instance from a string or a stringable structure or returns null on failure. + */ + public static function tryNew(Rfc3986Uri|WhatwgUrl|Stringable|string $uri = ''): ?self + { + try { + return self::new($uri); + } catch (UriException) { + return null; + } } /** @@ -120,16 +138,6 @@ final class Http implements Stringable, Psr7UriInterface, JsonSerializable return new self(Uri::fromServer($server)); } - /** - * Create a new instance from a URI and a Base URI. - * - * The returned URI must be absolute. - */ - public static function fromBaseUri(Stringable|string $uri, Stringable|string|null $baseUri = null): self - { - return new self(Uri::fromBaseUri($uri, $baseUri)); - } - /** * Creates a new instance from a template. * @@ -141,6 +149,16 @@ final class Http implements Stringable, Psr7UriInterface, JsonSerializable return new self(Uri::fromTemplate($template, $variables)); } + /** + * Returns a new instance from a URI and a Base URI.or null on failure. + * + * The returned URI must be absolute if a base URI is provided + */ + public static function parse(WhatWgUrl|Rfc3986Uri|Stringable|string $uri, WhatWgUrl|Rfc3986Uri|Stringable|string|null $baseUri = null): ?self + { + return null !== ($uri = Uri::parse($uri, $baseUri)) ? new self($uri) : null; + } + public function getScheme(): string { return $this->uri->getScheme() ?? ''; @@ -168,7 +186,12 @@ final class Http implements Stringable, Psr7UriInterface, JsonSerializable public function getPath(): string { - return $this->uri->getPath(); + $path = $this->uri->getPath(); + + return match (true) { + str_starts_with($path, '//') => '/'.ltrim($path, '/'), + default => $path, + }; } public function getQuery(): string @@ -210,6 +233,19 @@ final class Http implements Stringable, Psr7UriInterface, JsonSerializable }; } + public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static + { + if (!is_bool($condition)) { + $condition = $condition($this); + } + + return match (true) { + $condition => $onSuccess($this), + null !== $onFail => $onFail($this), + default => $this, + } ?? $this; + } + public function withScheme(string $scheme): self { return $this->newInstance($this->uri->withScheme($this->filterInput($scheme))); @@ -245,6 +281,23 @@ final class Http implements Stringable, Psr7UriInterface, JsonSerializable return $this->newInstance($this->uri->withFragment($this->filterInput($fragment))); } + /** + * DEPRECATION WARNING! This method will be removed in the next major point release. + * + * @deprecated Since version 7.6.0 + * @codeCoverageIgnore + * @see Http::parse() + * + * Create a new instance from a URI and a Base URI. + * + * The returned URI must be absolute. + */ + #[Deprecated(message:'use League\Uri\Http::parse() instead', since:'league/uri:7.6.0')] + public static function fromBaseUri(Rfc3986Uri|WhatwgUrl|Stringable|string $uri, Rfc3986Uri|WhatwgUrl|Stringable|string|null $baseUri = null): self + { + return new self(Uri::fromBaseUri($uri, $baseUri)); + } + /** * DEPRECATION WARNING! This method will be removed in the next major point release. * diff --git a/vendor/league/uri/SchemeType.php b/vendor/league/uri/SchemeType.php new file mode 100644 index 000000000..8539e26a4 --- /dev/null +++ b/vendor/league/uri/SchemeType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri; + +enum SchemeType +{ + case Opaque; + case Hierarchical; + case Unknown; + + public function isOpaque(): bool + { + return self::Opaque === $this; + } + + public function isHierarchical(): bool + { + return self::Hierarchical === $this; + } + + public function isUnknown(): bool + { + return self::Unknown === $this; + } +} diff --git a/vendor/league/uri/Uri.php b/vendor/league/uri/Uri.php index 00383428d..c607c2820 100644 --- a/vendor/league/uri/Uri.php +++ b/vendor/league/uri/Uri.php @@ -13,55 +13,83 @@ declare(strict_types=1); namespace League\Uri; +use Closure; use Deprecated; use finfo; +use League\Uri\Contracts\Conditionable; +use League\Uri\Contracts\FragmentDirective; use League\Uri\Contracts\UriComponentInterface; use League\Uri\Contracts\UriException; use League\Uri\Contracts\UriInterface; use League\Uri\Exceptions\ConversionFailed; use League\Uri\Exceptions\MissingFeature; use League\Uri\Exceptions\SyntaxError; -use League\Uri\Idna\Converter as IdnConverter; +use League\Uri\Idna\Converter as IdnaConverter; +use League\Uri\IPv4\Converter as IPv4Converter; +use League\Uri\IPv6\Converter as IPv6Converter; use League\Uri\UriTemplate\TemplateCanNotBeExpanded; use Psr\Http\Message\UriInterface as Psr7UriInterface; +use RuntimeException; use SensitiveParameter; +use SplFileInfo; +use SplFileObject; use Stringable; +use Throwable; +use TypeError; +use Uri\Rfc3986\Uri as Rfc3986Uri; +use Uri\WhatWg\Url as WhatWgUrl; use function array_filter; +use function array_key_last; use function array_map; +use function array_pop; use function base64_decode; use function base64_encode; +use function basename; use function count; +use function dirname; use function explode; +use function feof; use function file_get_contents; use function filter_var; +use function fread; use function implode; use function in_array; use function inet_pton; -use function ltrim; +use function is_bool; +use function is_string; use function preg_match; use function preg_replace_callback; +use function rawurldecode; use function rawurlencode; +use function restore_error_handler; +use function set_error_handler; +use function sprintf; use function str_contains; +use function str_repeat; use function str_replace; +use function str_starts_with; use function strlen; use function strpos; use function strspn; use function strtolower; use function substr; +use function trim; use const FILEINFO_MIME; +use const FILEINFO_MIME_TYPE; use const FILTER_FLAG_IPV4; use const FILTER_FLAG_IPV6; use const FILTER_NULL_ON_FAILURE; use const FILTER_VALIDATE_BOOLEAN; +use const FILTER_VALIDATE_EMAIL; use const FILTER_VALIDATE_IP; /** * @phpstan-import-type ComponentMap from UriString * @phpstan-import-type InputComponentMap from UriString */ -final class Uri implements UriInterface +final class Uri implements Conditionable, UriInterface { /** * RFC3986 invalid characters. @@ -72,15 +100,6 @@ final class Uri implements UriInterface */ private const REGEXP_INVALID_CHARS = '/[\x00-\x1f\x7f]/'; - /** - * RFC3986 schema regular expression pattern. - * - * @link https://tools.ietf.org/html/rfc3986#section-3.1 - * - * @var string - */ - private const REGEXP_SCHEME = ',^[a-z]([-a-z\d+.]+)?$,i'; - /** * RFC3986 host identified by a registered name regular expression pattern. * @@ -135,7 +154,7 @@ final class Uri implements UriInterface /** * Regular expression pattern to for file URI. * contains the volume but not the volume separator. - * The volume separator may be URL-encoded (`|` as `%7C`) by ::formatPath(), + * The volume separator may be URL-encoded (`|` as `%7C`) by formatPath(), * so we account for that here. * * @var string @@ -161,29 +180,13 @@ final class Uri implements UriInterface private const REGEXP_BINARY = ',(;|^)base64$,'; /** - * Windows file path string regular expression pattern. + * Windows filepath regular expression pattern. * contains both the volume and volume separator. * * @var string */ private const REGEXP_WINDOW_PATH = ',^(?[a-zA-Z][:|\|]),'; - /** - * Supported schemes and corresponding default port. - * - * @var array - */ - private const SCHEME_DEFAULT_PORT = [ - 'data' => null, - 'file' => null, - 'ftp' => 21, - 'gopher' => 70, - 'http' => 80, - 'https' => 443, - 'ws' => 80, - 'wss' => 443, - ]; - /** * Maximum number of cached items. * @@ -208,7 +211,9 @@ final class Uri implements UriInterface private readonly string $path; private readonly ?string $query; private readonly ?string $fragment; - private readonly string $uri; + private readonly string $uriAsciiString; + private readonly string $uriUnicodeString; + private readonly ?string $origin; private function __construct( ?string $scheme, @@ -228,11 +233,22 @@ final class Uri implements UriInterface $this->path = $this->formatPath($path); $this->query = Encoder::encodeQueryOrFragment($query); $this->fragment = Encoder::encodeQueryOrFragment($fragment); - $this->userInfo = $this->formatUserInfo($this->user, $this->pass); + $this->userInfo = null !== $this->pass ? $this->user.':'.$this->pass : $this->user; $this->authority = UriString::buildAuthority($this->toComponents()); - $this->uri = UriString::buildUri($this->scheme, $this->authority, $this->path, $this->query, $this->fragment); - + $this->uriAsciiString = UriString::buildUri($this->scheme, $this->authority, $this->path, $this->query, $this->fragment); + $this->assertValidRfc3986Uri(); $this->assertValidState(); + $this->origin = $this->setOrigin(); + $host = $this->getUnicodeHost(); + $this->uriUnicodeString = $host === $this->host + ? $this->uriAsciiString + : UriString::buildUri( + $this->scheme, + UriString::buildAuthority([...$this->toComponents(), ...['host' => $host]]), + $this->path, + $this->query, + $this->fragment + ); } /** @@ -252,12 +268,10 @@ final class Uri implements UriInterface return $formattedScheme; } - if ( - !array_key_exists($formattedScheme, self::SCHEME_DEFAULT_PORT) - && 1 !== preg_match(self::REGEXP_SCHEME, $formattedScheme) - ) { - throw new SyntaxError('The scheme `'.$scheme.'` is invalid.'); - } + null !== UriScheme::tryFrom($formattedScheme) + || UriString::isValidScheme($formattedScheme) + || throw new SyntaxError('The scheme `'.$scheme.'` is invalid.'); + $cache[$formattedScheme] = 1; if (self::MAXIMUM_CACHED_ITEMS < count($cache)) { @@ -267,19 +281,6 @@ final class Uri implements UriInterface return $formattedScheme; } - /** - * Set the UserInfo component. - */ - private function formatUserInfo( - ?string $user, - #[SensitiveParameter] ?string $password - ): ?string { - return match (null) { - $password => $user, - default => $user.':'.$password, - }; - } - /** * Validate and Format the Host component. */ @@ -315,12 +316,19 @@ final class Uri implements UriInterface private function formatRegisteredName(string $host): string { $formattedHost = rawurldecode($host); + if ($formattedHost === $host) { + return match (1) { + preg_match(self::REGEXP_HOST_REGNAME, $formattedHost) => $formattedHost, + preg_match(self::REGEXP_HOST_GEN_DELIMS, $formattedHost) => throw new SyntaxError('The host `'.$host.'` is invalid : a registered name cannot contain URI delimiters or spaces.'), + default => IdnaConverter::toAsciiOrFail($host), + }; + } - return match (1) { - preg_match(self::REGEXP_HOST_REGNAME, $formattedHost) => $formattedHost, - preg_match(self::REGEXP_HOST_GEN_DELIMS, $formattedHost) => throw new SyntaxError('The host `'.$host.'` is invalid : a registered name cannot contain URI delimiters or spaces.'), - default => IdnConverter::toAsciiOrFail($host), - }; + if (IdnaConverter::toAscii($formattedHost)->hasErrors()) { + throw new SyntaxError('The host `'.$host.'` is invalid : the registered name contains invalid characters.'); + } + + return (string) Encoder::normalizeHost($host); } /** @@ -369,7 +377,9 @@ final class Uri implements UriInterface */ private function formatPort(?int $port = null): ?int { - $defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null; + $defaultPort = null !== $this->scheme + ? UriScheme::tryFrom($this->scheme)?->port() + : null; return match (true) { null === $port, $defaultPort === $port => null, @@ -379,46 +389,86 @@ final class Uri implements UriInterface } /** - * Create a new instance from a string. + * Create a new instance from a string or a stringable structure or returns null on failure. */ - public static function new(Stringable|string $uri = ''): self + public static function tryNew(Rfc3986Uri|WhatWgUrl|Urn|Stringable|string $uri = ''): ?self { - $components = match (true) { - $uri instanceof UriInterface => $uri->toComponents(), - default => UriString::parse($uri), - }; - - return new self( - $components['scheme'], - $components['user'], - $components['pass'], - $components['host'], - $components['port'], - $components['path'], - $components['query'], - $components['fragment'] - ); + try { + return self::new($uri); + } catch (Throwable) { + return null; + } } /** - * Creates a new instance from a URI and a Base URI. - * - * The returned URI must be absolute. + * Create a new instance from a string. */ - public static function fromBaseUri( - Stringable|string $uri, - Stringable|string|null $baseUri = null - ): self { - $uri = self::new($uri); - $baseUri = BaseUri::from($baseUri ?? $uri); + public static function new(Rfc3986Uri|WhatWgUrl|Urn|Stringable|string $uri = ''): self + { + if ($uri instanceof Rfc3986Uri) { + return new self( + $uri->getRawScheme(), + $uri->getRawUsername(), + $uri->getRawPassword(), + $uri->getRawHost(), + $uri->getPort(), + $uri->getRawPath(), + $uri->getRawQuery(), + $uri->getRawFragment() + ); + } - /** @var self $uri */ - $uri = match (true) { - $baseUri->isAbsolute() => $baseUri->resolve($uri)->getUri(), - default => throw new SyntaxError('the URI `'.$baseUri.'` must be absolute.'), - }; + if ($uri instanceof WhatWgUrl) { + return new self( + $uri->getScheme(), + $uri->getUsername(), + $uri->getPassword(), + $uri->getAsciiHost(), + $uri->getPort(), + $uri->getPath(), + $uri->getQuery(), + $uri->getFragment(), + ); + } - return $uri; + $uri = (string) $uri; + trim($uri) === $uri || throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri)); + + return new self(...UriString::parse(str_replace(' ', '%20', $uri))); + } + + /** + * Returns a new instance from a URI and a Base URI.or null on failure. + * + * The returned URI must be absolute if a base URI is provided + */ + public static function parse(Rfc3986Uri|WhatWgUrl|Urn|Stringable|string $uri, Rfc3986Uri|WhatWgUrl|Urn|Stringable|string|null $baseUri = null): ?self + { + try { + if (null === $baseUri) { + return self::new($uri); + } + + if ($uri instanceof Rfc3986Uri) { + $uri = $uri->toRawString(); + } + + if ($uri instanceof WhatWgUrl) { + $uri = $uri->toAsciiString(); + } + + if ($baseUri instanceof Rfc3986Uri) { + $baseUri = $baseUri->toRawString(); + } + + if ($baseUri instanceof WhatWgUrl) { + $baseUri = $baseUri->toAsciiString(); + } + + return self::new(UriString::resolve($uri, $baseUri)); + } catch (Throwable) { + return null; + } } /** @@ -430,7 +480,7 @@ final class Uri implements UriInterface public static function fromTemplate(UriTemplate|Stringable|string $template, iterable $variables = []): self { return match (true) { - $template instanceof UriTemplate => self::fromComponents($template->expand($variables)->toComponents()), + $template instanceof UriTemplate => self::new($template->expand($variables)), $template instanceof UriTemplate\Template => self::new($template->expand($variables)), default => self::new(UriTemplate\Template::new($template)->expand($variables)), }; @@ -468,32 +518,66 @@ final class Uri implements UriInterface /** * Create a new instance from a data file path. * - * @param resource|null $context + * @param SplFileInfo|SplFileObject|resource|Stringable|string $path + * @param ?resource $context * * @throws MissingFeature If ext/fileinfo is not installed * @throws SyntaxError If the file does not exist or is not readable */ - public static function fromFileContents(Stringable|string $path, $context = null): self + public static function fromFileContents(mixed $path, $context = null): self { FeatureDetection::supportsFileDetection(); + $finfo = new finfo(FILEINFO_MIME_TYPE); + $bufferSize = 8192; - $path = (string) $path; - $fileArguments = [$path, false]; - $mimeArguments = [$path, FILEINFO_MIME]; - if (null !== $context) { - $fileArguments[] = $context; - $mimeArguments[] = $context; - } + /** @var Closure(SplFileobject): array{0:string, 1:string} $fromFileObject */ + $fromFileObject = function (SplFileObject $path) use ($finfo, $bufferSize): array { + $raw = $path->fread($bufferSize); + false !== $raw || throw new SyntaxError('The file `'.$path.'` does not exist or is not readable.'); - set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); - $raw = file_get_contents(...$fileArguments); - restore_error_handler(); + $mimetype = (string) $finfo->buffer($raw); + while (!$path->eof()) { + $raw .= $path->fread($bufferSize); + } - if (false === $raw) { - throw new SyntaxError('The file `'.$path.'` does not exist or is not readable.'); - } + return [$mimetype, $raw]; + }; - $mimetype = (string) (new finfo(FILEINFO_MIME))->file(...$mimeArguments); + /** @var Closure(resource): array{0:string, 1:string} $fromResource */ + $fromResource = function ($stream) use ($finfo, $path, $bufferSize): array { + set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); + $raw = fread($stream, $bufferSize); + false !== $raw || throw new SyntaxError('The file `'.$path.'` does not exist or is not readable.'); + + $mimetype = (string) $finfo->buffer($raw); + while (!feof($stream)) { + $raw .= fread($stream, $bufferSize); + } + restore_error_handler(); + + return [$mimetype, $raw]; + }; + + /** @var Closure(Stringable|string, resource|null): array{0:string, 1:string} $fromPath */ + $fromPath = function (Stringable|string $path, $context) use ($finfo): array { + $path = (string) $path; + set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); + $raw = file_get_contents(filename: $path, context: $context); + restore_error_handler(); + false !== $raw || throw new SyntaxError('The file `'.$path.'` does not exist or is not readable.'); + $mimetype = (string) $finfo->file(filename: $path, flags: FILEINFO_MIME, context: $context); + + return [$mimetype, $raw]; + }; + + [$mimetype, $raw] = match (true) { + $path instanceof SplFileObject => $fromFileObject($path), + $path instanceof SplFileInfo => $fromFileObject($path->openFile(mode: 'rb', context: $context)), + is_resource($path) => $fromResource($path), + $path instanceof Stringable, + is_string($path) => $fromPath($path, $context), + default => throw new TypeError('The path `'.$path.'` is not a valid resource.'), + }; return Uri::fromComponents([ 'scheme' => 'data', @@ -506,7 +590,7 @@ final class Uri implements UriInterface * * @throws SyntaxError If the parameter syntax is invalid */ - public static function fromData(string $data, string $mimetype = '', string $parameters = ''): self + public static function fromData(Stringable|string $data, string $mimetype = '', string $parameters = ''): self { static $regexpMimetype = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,'; @@ -516,6 +600,7 @@ final class Uri implements UriInterface default => throw new SyntaxError('Invalid mimeType, `'.$mimetype.'`.'), }; + $data = (string) $data; if ('' === $parameters) { return self::fromComponents([ 'scheme' => 'data', @@ -560,8 +645,8 @@ final class Uri implements UriInterface */ public static function fromWindowsPath(Stringable|string $path): self { - $path = (string) $path; $root = ''; + $path = (string) $path; if (1 === preg_match(self::REGEXP_WINDOW_PATH, $path, $matches)) { $root = substr($matches['root'], 0, -1).':'; $path = substr($path, strlen($root)); @@ -589,7 +674,7 @@ final class Uri implements UriInterface * * @see https://datatracker.ietf.org/doc/html/rfc8089 */ - public static function fromRfc8089(Stringable|string $uri): UriInterface + public static function fromRfc8089(Stringable|string $uri): static { $fileUri = self::new((string) preg_replace(',^(file:/)([^/].*)$,i', 'file:///$2', (string) $uri)); $scheme = $fileUri->getScheme(); @@ -630,7 +715,7 @@ final class Uri implements UriInterface /** * Returns the environment user info. * - * @return non-empty-array{0: ?string, 1: ?string} + * @return non-empty-array {0: ?string, 1: ?string} */ private static function fetchUserInfo(array $server): array { @@ -639,9 +724,7 @@ final class Uri implements UriInterface $pass = $server['PHP_AUTH_PW']; if (str_starts_with(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) { $userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true); - if (false === $userinfo) { - throw new SyntaxError('The user info could not be detected'); - } + false !== $userinfo || throw new SyntaxError('The user info could not be detected'); [$user, $pass] = explode(':', $userinfo, 2) + [1 => null]; } @@ -679,10 +762,7 @@ final class Uri implements UriInterface return [$matches['host'], $matches['port'] ?? $server['SERVER_PORT']]; } - if (!isset($server['SERVER_ADDR'])) { - throw new SyntaxError('The host could not be detected'); - } - + isset($server['SERVER_ADDR']) || throw new SyntaxError('The host could not be detected'); if (false === filter_var($server['SERVER_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return ['['.$server['SERVER_ADDR'].']', $server['SERVER_PORT']]; } @@ -719,7 +799,7 @@ final class Uri implements UriInterface { return match ($this->scheme) { 'data' => Encoder::encodePath(self::formatDataPath($path)), - 'file' => $this->formatFilePath(Encoder::encodePath($path)), + 'file' => self::formatFilePath(Encoder::encodePath($path)), default => Encoder::encodePath($path), }; } @@ -768,20 +848,14 @@ final class Uri implements UriInterface */ private static function assertValidPath(string $mimetype, string $parameters, string $data): void { - if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) { - throw new SyntaxError('The path mimetype `'.$mimetype.'` is invalid.'); - } - + 1 === preg_match(self::REGEXP_MIMETYPE, $mimetype) || throw new SyntaxError('The path mimetype `'.$mimetype.'` is invalid.'); $isBinary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches); if ($isBinary) { $parameters = substr($parameters, 0, - strlen($matches[0])); } $res = array_filter(array_filter(explode(';', $parameters), self::validateParameter(...))); - if ([] !== $res) { - throw new SyntaxError('The path parameters `'.$parameters.'` is invalid.'); - } - + [] === $res || throw new SyntaxError('The path parameters `'.$parameters.'` is invalid.'); if (!$isBinary) { return; } @@ -803,9 +877,9 @@ final class Uri implements UriInterface } /** - * Format path component for file scheme. + * Format the path component for the URI scheme file. */ - private function formatFilePath(string $path): string + private static function formatFilePath(string $path): string { return (string) preg_replace_callback( self::REGEXP_FILE_PATH, @@ -820,17 +894,16 @@ final class Uri implements UriInterface * @link https://tools.ietf.org/html/rfc3986#section-3 * @link https://tools.ietf.org/html/rfc3986#section-3.3 * - * @throws SyntaxError if the URI is in an invalid state according to RFC3986 - * @throws SyntaxError if the URI is in an invalid state according to scheme specific rules + * @throws SyntaxError if the URI is in an invalid state, according to RFC3986 */ - private function assertValidState(): void + private function assertValidRfc3986Uri(): void { if (null !== $this->authority && ('' !== $this->path && '/' !== $this->path[0])) { throw new SyntaxError('If an authority is present the path must be empty or start with a `/`.'); } if (null === $this->authority && str_starts_with($this->path, '//')) { - throw new SyntaxError('If there is no authority the path `'.$this->path.'` cannot start with a `//`.'); + throw new SyntaxError('If there is no authority the path `' . $this->path . '` cannot start with a `//`.'); } $pos = strpos($this->path, ':'); @@ -841,16 +914,195 @@ final class Uri implements UriInterface ) { throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.'); } + } - if (! match ($this->scheme) { - 'data' => $this->isUriWithSchemeAndPathOnly(), - 'file' => $this->isUriWithSchemeHostAndPathOnly(), - 'ftp', 'gopher' => $this->isNonEmptyHostUriWithoutFragmentAndQuery(), - 'http', 'https' => $this->isNonEmptyHostUri(), - 'ws', 'wss' => $this->isNonEmptyHostUriWithoutFragment(), - default => true, - }) { - throw new SyntaxError('The uri `'.$this->uri.'` is invalid for the `'.$this->scheme.'` scheme.'); + /** + * assert the URI scheme is valid + * + * @link https://w3c.github.io/FileAPI/#url + * @link https://datatracker.ietf.org/doc/html/rfc2397 + * @link https://tools.ietf.org/html/rfc3986#section-3 + * @link https://tools.ietf.org/html/rfc3986#section-3.3 + * + * @throws SyntaxError if the URI is in an invalid state, according to scheme-specific rules + */ + private function assertValidState(): void + { + $scheme = UriScheme::tryFrom((string) $this->scheme); + if (null === $scheme) { + return; + } + + $schemeType = $scheme->type(); + match ($scheme) { + UriScheme::Blob => $this->isValidBlob(), + UriScheme::Mailto => $this->isValidMailto(), + UriScheme::Data, + UriScheme::About, + UriScheme::Javascript => $this->isUriWithSchemeAndPathOnly(), + UriScheme::File => $this->isUriWithSchemeHostAndPathOnly(), + UriScheme::Ftp, + UriScheme::Gopher, + UriScheme::Afp, + UriScheme::Dict, + UriScheme::Msrps, + UriScheme::Msrp, + UriScheme::Mtqp, + UriScheme::Rsync, + UriScheme::Ssh, + UriScheme::Svn, + UriScheme::Snmp => $this->isNonEmptyHostUriWithoutFragmentAndQuery(), + UriScheme::Https, + UriScheme::Http => $this->isNonEmptyHostUri(), + UriScheme::Ws, + UriScheme::Wss, + UriScheme::Ipp, + UriScheme::Ipps => $this->isNonEmptyHostUriWithoutFragment(), + UriScheme::Ldap, + UriScheme::Ldaps, + UriScheme::Acap, + UriScheme::Imaps, + UriScheme::Imap, + UriScheme::Redis => null === $this->fragment, + UriScheme::Prospero => null === $this->fragment && null === $this->query && null === $this->userInfo, + UriScheme::Urn => null !== Urn::parse($this->uriAsciiString), + UriScheme::Telnet, + UriScheme::Tn3270 => null === $this->fragment && null === $this->query && in_array($this->path, ['', '/'], true), + UriScheme::Vnc => null !== $this->authority && null === $this->fragment && '' === $this->path, + default => $schemeType->isUnknown() + || ($schemeType->isOpaque() && null === $this->authority) + || ($schemeType->isHierarchical() && null !== $this->authority), + } || throw new SyntaxError('The uri `'.$this->uriAsciiString.'` is invalid for the `'.$this->scheme.'` scheme.'); + } + + private function isValidBlob(): bool + { + static $regexpUuidRfc4122 = '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i'; + + if (!$this->isUriWithSchemeAndPathOnly() + || '' === $this->path + || !str_contains($this->path, '/') + || str_ends_with($this->path, '/') + || 1 !== preg_match($regexpUuidRfc4122, basename($this->path)) + ) { + return false; + } + + $origin = dirname($this->path); + if ('null' === $origin) { + return true; + } + + try { + $components = UriString::parse($origin); + + return '' === $components['path'] + && null === $components['query'] + && null === $components['fragment'] + && true === UriScheme::tryFrom((string) $components['scheme'])?->isWhatWgSpecial(); + } catch (UriException) { + return false; + } + } + + private function isValidMailto(): bool + { + if (null !== $this->authority || null !== $this->fragment || str_contains((string) $this->query, '?')) { + return false; + } + + static $mailHeaders = [ + 'to', 'cc', 'bcc', 'reply-to', 'from', 'sender', + 'resent-to', 'resent-cc', 'resent-bcc', 'resent-from', 'resent-sender', + 'return-path', 'delivery-to', 'site-owner', + ]; + + static $headerRegexp = '/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D'; + $pairs = QueryString::parseFromValue($this->query); + $hasTo = false; + foreach ($pairs as [$name, $value]) { + $headerName = strtolower($name); + if (in_array($headerName, $mailHeaders, true)) { + if (null === $value || !self::validateEmailList($value)) { + return false; + } + + if (!$hasTo && 'to' === $headerName) { + $hasTo = true; + } + continue; + } + + if (1 !== preg_match($headerRegexp, (string) Encoder::decodeAll($name))) { + return false; + } + } + + return '' === $this->path ? $hasTo : self::validateEmailList($this->path); + } + + private static function validateEmailList(string $emails): bool + { + foreach (explode(',', $emails) as $email) { + if (false === filter_var((string) Encoder::decodeAll($email), FILTER_VALIDATE_EMAIL)) { + return false; + } + } + + return '' !== $emails; + } + + /** + * Sets the URI origin. + * + * The origin read-only property of the URL interface returns a string containing + * the Unicode serialization of the represented URL. + */ + private function setOrigin(): ?string + { + try { + if ('blob' !== $this->scheme) { + if (!(UriScheme::tryFrom($this->scheme ?? '')?->isWhatWgSpecial() ?? false)) { + return null; + } + + $host = $this->host; + $converted = $host; + if (null !== $converted) { + try { + $converted = IPv4Converter::fromEnvironment()->toDecimal($host); + } catch (MissingFeature) { + $converted = null; + } + + if (false === filter_var($converted, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $converted = IPv6Converter::compress($host); + } + + /** @var string $converted */ + if ($converted !== $host) { + $converted = Idna\Converter::toAscii($converted)->domain(); + } + } + + return $this + ->withFragment(null) + ->withQuery(null) + ->withPath('') + ->withUserInfo(null) + ->withHost($converted) + ->toString(); + } + + $components = UriString::parse($this->path); + $scheme = strtolower($components['scheme'] ?? ''); + if (! (UriScheme::tryFrom($scheme)?->isWhatWgSpecial() ?? false)) { + return null; + } + + return self::fromComponents($components)->origin; + } catch (UriException) { + return null; } } @@ -903,21 +1155,16 @@ final class Uri implements UriInterface return $this->isNonEmptyHostUri() && null === $this->fragment && null === $this->query; } - public function toString(): string - { - return $this->uri; - } - - /** - * {@inheritDoc} - */ public function __toString(): string { return $this->toString(); } /** - * {@inheritDoc} + * Returns the string representation as a URI reference. + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @see ::toString */ public function jsonSerialize(): string { @@ -925,6 +1172,169 @@ final class Uri implements UriInterface } /** + * Returns the string representation as a URI reference. + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + */ + public function toString(): string + { + return $this->toAsciiString(); + } + + /** + * Returns the string representation as a URI reference. + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + */ + public function toAsciiString(): string + { + return $this->uriAsciiString; + } + + /** + * Returns the string representation as a URI reference. + * + * The host is converted to its UNICODE representation if available + */ + public function toUnicodeString(): string + { + return $this->uriUnicodeString; + } + + /** + * Returns the human-readable string representation of the URI as an IRI. + * + * @see https://datatracker.ietf.org/doc/html/rfc3987 + */ + public function toDisplayString(): string + { + return UriString::toIriString($this->toString()); + } + + /** + * Returns the Unix filesystem path. + * + * The method will return null if a scheme is present and is not the `file` scheme + */ + public function toUnixPath(): ?string + { + return match ($this->scheme) { + 'file', null => rawurldecode($this->path), + default => null, + }; + } + + /** + * Returns the Windows filesystem path. + * + * The method will return null if a scheme is present and is not the `file` scheme + */ + public function toWindowsPath(): ?string + { + static $regexpWindowsPath = ',^(?[a-zA-Z]:),'; + + if (!in_array($this->scheme, ['file', null], true)) { + return null; + } + + $originalPath = $this->path; + $path = $originalPath; + if ('/' === ($path[0] ?? '')) { + $path = substr($path, 1); + } + + if (1 === preg_match($regexpWindowsPath, $path, $matches)) { + $root = $matches['root']; + $path = substr($path, strlen($root)); + + return $root.str_replace('/', '\\', rawurldecode($path)); + } + + $host = $this->host; + + return match (null) { + $host => str_replace('/', '\\', rawurldecode($originalPath)), + default => '\\\\'.$host.'\\'.str_replace('/', '\\', rawurldecode($path)), + }; + } + + /** + * Returns a string representation of a File URI according to RFC8089. + * + * The method will return null if the URI scheme is not the `file` scheme + * + * @see https://datatracker.ietf.org/doc/html/rfc8089 + */ + public function toRfc8089(): ?string + { + $path = $this->path; + + return match (true) { + 'file' !== $this->scheme => null, + in_array($this->authority, ['', null, 'localhost'], true) => 'file:'.match (true) { + '' === $path, + '/' === $path[0] => $path, + default => '/'.$path, + }, + default => $this->toString(), + }; + } + + /** + * Save the data to a specific file. + * + * The method returns the number of bytes written to the file + * or null for any other scheme except the data scheme + * + * @param SplFileInfo|SplFileObject|resource|Stringable|string $destination + * @param ?resource $context + * + * @throws RuntimeException if the content cannot be stored. + */ + public function toFileContents(mixed $destination, $context = null): ?int + { + if ('data' !== $this->scheme) { + return null; + } + + [$mediaType, $document] = explode(',', $this->path, 2) + [0 => '', 1 => null]; + null !== $document || throw new RuntimeException('Unable to extract the document part from the URI path.'); + + $data = match (true) { + str_ends_with((string) $mediaType, ';base64') => (string) base64_decode($document, true), + default => rawurldecode($document), + }; + + $res = match (true) { + $destination instanceof SplFileObject => $destination->fwrite($data), + $destination instanceof SplFileInfo => $destination->openFile(mode:'wb', context: $context)->fwrite($data), + is_resource($destination) => fwrite($destination, $data), + $destination instanceof Stringable, + is_string($destination) => (function () use ($destination, $data, $context): int|false { + set_error_handler(fn (int $errno, string $errstr, string $errfile, int $errline) => true); + $rsrc = fopen((string) $destination, mode:'wb', context: $context); + if (false === $rsrc) { + restore_error_handler(); + throw new RuntimeException('Unable to open the destination file: '.$destination); + } + + $bytes = fwrite($rsrc, $data); + fclose($rsrc); + restore_error_handler(); + + return $bytes; + })(), + default => throw new TypeError('Unsupported destination type; expected SplFileObject, SplFileInfo, resource or a string; '.(is_object($destination) ? $destination::class : gettype($destination)).' given.'), + }; + + false !== $res || throw new RuntimeException('Unable to write to the destination file.'); + + return $res; + } + + /** + * Returns an associative array containing all the URI components. + * * @return ComponentMap */ public function toComponents(): array @@ -941,108 +1351,100 @@ final class Uri implements UriInterface ]; } - /** - * {@inheritDoc} - */ public function getScheme(): ?string { return $this->scheme; } - /** - * {@inheritDoc} - */ public function getAuthority(): ?string { return $this->authority; } /** - * {@inheritDoc} + * Returns the user component encoded value. + * + * @see https://wiki.php.net/rfc/url_parsing_api */ public function getUsername(): ?string { return $this->user; } - /** - * {@inheritDoc} - */ public function getPassword(): ?string { return $this->pass; } - /** - * {@inheritDoc} - */ public function getUserInfo(): ?string { return $this->userInfo; } - /** - * {@inheritDoc} - */ public function getHost(): ?string { return $this->host; } - /** - * {@inheritDoc} - */ + public function getUnicodeHost(): ?string + { + if (null === $this->host) { + return null; + } + + $host = IdnaConverter::toUnicode($this->host)->domain(); + if ($host === $this->host) { + return $this->host; + } + + return $host; + } + public function getPort(): ?int { return $this->port; } - /** - * {@inheritDoc} - */ public function getPath(): string { - return match (true) { - str_starts_with($this->path, '//') => '/'.ltrim($this->path, '/'), - default => $this->path, - }; + return $this->path; } - /** - * {@inheritDoc} - */ public function getQuery(): ?string { return $this->query; } - /** - * {@inheritDoc} - */ public function getFragment(): ?string { return $this->fragment; } - /** - * {@inheritDoc} - */ - public function withScheme(Stringable|string|null $scheme): UriInterface + public function getOrigin(): ?string + { + return $this->origin; + } + + public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static + { + if (!is_bool($condition)) { + $condition = $condition($this); + } + + return match (true) { + $condition => $onSuccess($this), + null !== $onFail => $onFail($this), + default => $this, + } ?? $this; + } + + public function withScheme(Stringable|string|null $scheme): static { $scheme = $this->formatScheme($this->filterString($scheme)); return match ($scheme) { $this->scheme => $this, - default => new self( - $scheme, - $this->user, - $this->pass, - $this->host, - $this->port, - $this->path, - $this->query, - $this->fragment, - ), + default => new self($scheme, $this->user, $this->pass, $this->host, $this->port, $this->path, $this->query, $this->fragment), }; } @@ -1069,65 +1471,51 @@ final class Uri implements UriInterface public function withUserInfo( Stringable|string|null $user, #[SensitiveParameter] Stringable|string|null $password = null - ): UriInterface { + ): static { $user = Encoder::encodeUser($this->filterString($user)); $pass = Encoder::encodePassword($this->filterString($password)); - $userInfo = ('' !== $user) ? $this->formatUserInfo($user, $pass) : null; + $userInfo = $user; + if (null !== $password) { + $userInfo .= ':'.$pass; + } return match ($userInfo) { $this->userInfo => $this, - default => new self( - $this->scheme, - $user, - $pass, - $this->host, - $this->port, - $this->path, - $this->query, - $this->fragment, - ), + default => new self($this->scheme, $user, $pass, $this->host, $this->port, $this->path, $this->query, $this->fragment), }; } - public function withHost(Stringable|string|null $host): UriInterface + public function withUsername(Stringable|string|null $user): static + { + return $this->withUserInfo($user, $this->pass); + } + + public function withPassword(#[SensitiveParameter] Stringable|string|null $password): static + { + return $this->withUserInfo($this->user, $password); + } + + public function withHost(Stringable|string|null $host): static { $host = $this->formatHost($this->filterString($host)); return match ($host) { $this->host => $this, - default => new self( - $this->scheme, - $this->user, - $this->pass, - $host, - $this->port, - $this->path, - $this->query, - $this->fragment, - ), + default => new self($this->scheme, $this->user, $this->pass, $host, $this->port, $this->path, $this->query, $this->fragment), }; } - public function withPort(int|null $port): UriInterface + public function withPort(int|null $port): static { $port = $this->formatPort($port); return match ($port) { $this->port => $this, - default => new self( - $this->scheme, - $this->user, - $this->pass, - $this->host, - $port, - $this->path, - $this->query, - $this->fragment, - ), + default => new self($this->scheme, $this->user, $this->pass, $this->host, $port, $this->path, $this->query, $this->fragment), }; } - public function withPath(Stringable|string $path): UriInterface + public function withPath(Stringable|string $path): static { $path = $this->formatPath( $this->filterString($path) ?? throw new SyntaxError('The path component cannot be null.') @@ -1135,57 +1523,341 @@ final class Uri implements UriInterface return match ($path) { $this->path => $this, - default => new self( - $this->scheme, - $this->user, - $this->pass, - $this->host, - $this->port, - $path, - $this->query, - $this->fragment, - ), + default => new self($this->scheme, $this->user, $this->pass, $this->host, $this->port, $path, $this->query, $this->fragment), }; } - public function withQuery(Stringable|string|null $query): UriInterface + public function withQuery(Stringable|string|null $query): static { $query = Encoder::encodeQueryOrFragment($this->filterString($query)); return match ($query) { $this->query => $this, - default => new self( - $this->scheme, - $this->user, - $this->pass, - $this->host, - $this->port, - $this->path, - $query, - $this->fragment, - ), + default => new self($this->scheme, $this->user, $this->pass, $this->host, $this->port, $this->path, $query, $this->fragment), }; } - public function withFragment(Stringable|string|null $fragment): UriInterface + public function withFragment(Stringable|string|null $fragment): static { + if ($fragment instanceof FragmentDirective) { + $fragment = ':~:'.$fragment->toString(); + } + $fragment = Encoder::encodeQueryOrFragment($this->filterString($fragment)); return match ($fragment) { $this->fragment => $this, - default => new self( - $this->scheme, - $this->user, - $this->pass, - $this->host, - $this->port, - $this->path, - $this->query, - $fragment, - ), + default => new self($this->scheme, $this->user, $this->pass, $this->host, $this->port, $this->path, $this->query, $fragment), }; } + /** + * Tells whether the `file` scheme base URI represents a local file. + */ + public function isLocalFile(): bool + { + return match (true) { + 'file' !== $this->scheme => false, + in_array($this->authority, ['', null, 'localhost'], true) => true, + default => false, + }; + } + + /** + * Tells whether the URI is opaque or not. + * + * A URI is opaque if and only if it is absolute + * and does not have an authority path. + */ + public function isOpaque(): bool + { + return null === $this->authority + && null !== $this->scheme; + } + + /** + * Tells whether two URI do not share the same origin. + */ + public function isCrossOrigin(Rfc3986Uri|WhatWgUrl|Urn|Stringable|string $uri): bool + { + if (null === $this->origin) { + return true; + } + + $uri = self::tryNew($uri); + if (null === $uri || null === ($origin = $uri->getOrigin())) { + return true; + } + + return $this->origin !== $origin; + } + + public function isSameOrigin(Rfc3986Uri|WhatWgUrl|Urn|Stringable|string $uri): bool + { + return ! $this->isCrossOrigin($uri); + } + + /** + * Tells whether the URI is absolute. + */ + public function isAbsolute(): bool + { + return null !== $this->scheme; + } + + /** + * Tells whether the URI is a network path. + */ + public function isNetworkPath(): bool + { + return null === $this->scheme + && null !== $this->authority; + } + + /** + * Tells whether the URI is an absolute path. + */ + public function isAbsolutePath(): bool + { + return null === $this->scheme + && null === $this->authority + && '/' === ($this->path[0] ?? ''); + } + + /** + * Tells whether the URI is a relative path. + */ + public function isRelativePath(): bool + { + return null === $this->scheme + && null === $this->authority + && '/' !== ($this->path[0] ?? ''); + } + + /** + * Tells whether both URIs refer to the same document. + */ + public function isSameDocument(Rfc3986Uri|WhatWgUrl|UriInterface|Stringable|Urn|string $uri): bool + { + return $this->equals($uri); + } + + public function equals(Rfc3986Uri|WhatWgUrl|UriInterface|Stringable|Urn|string $uri, UriComparisonMode $uriComparisonMode = UriComparisonMode::ExcludeFragment): bool + { + if (!$uri instanceof UriInterface && !$uri instanceof Rfc3986Uri && !$uri instanceof WhatWgUrl) { + $uri = self::tryNew($uri); + } + + if (null === $uri) { + return false; + } + + $baseUri = $this; + if (UriComparisonMode::ExcludeFragment === $uriComparisonMode) { + $uri = $uri->withFragment(null); + $baseUri = $baseUri->withFragment(null); + } + + return $baseUri->normalize()->toString() === match (true) { + $uri instanceof Rfc3986Uri => $uri->toString(), + $uri instanceof WhatWgUrl => $uri->toAsciiString(), + default => $uri->normalize()->toString(), + }; + } + + /** + * Normalize a URI by applying non-destructive and destructive normalization + * rules as defined in RFC3986 and RFC3987. + */ + public function normalize(): static + { + $uriString = $this->toString(); + if ('' === $uriString) { + return $this; + } + + $normalizedUriString = UriString::normalize($uriString); + $normalizedUri = self::new($normalizedUriString); + if (null !== $normalizedUri->getAuthority() && ('' === $normalizedUri->getPath() && (UriScheme::tryFrom($normalizedUri->getScheme() ?? '')?->isWhatWgSpecial() ?? false))) { + $normalizedUri = $normalizedUri->withPath('/'); + } + + if ($normalizedUri->toString() === $uriString) { + return $this; + } + + return $normalizedUri; + } + + /** + * Resolves a URI against a base URI using RFC3986 rules. + * + * This method MUST retain the state of the submitted URI instance, and return + * a URI instance of the same type that contains the applied modifications. + * + * This method MUST be transparent when dealing with errors and exceptions. + * It MUST not alter or silence them apart from validating its own parameters. + */ + public function resolve(Rfc3986Uri|WhatWgUrl|UriInterface|Stringable|Urn|string $uri): static + { + return self::new(UriString::resolve( + match (true) { + $uri instanceof UriInterface, + $uri instanceof Rfc3986Uri => $uri->toString(), + $uri instanceof WhatWgUrl => $uri->toAsciiString(), + default => $uri, + }, + $this->toString() + )); + } + + /** + * Relativize a URI according to a base URI. + * + * This method MUST retain the state of the submitted URI instance, and return + * a URI instance of the same type that contains the applied modifications. + * + * This method MUST be transparent when dealing with error and exceptions. + * It MUST not alter of silence them apart from validating its own parameters. + */ + public function relativize(Rfc3986Uri|WhatWgUrl|UriInterface|Stringable|Urn|string $uri): static + { + $uri = self::new($uri); + + if ( + $this->scheme !== $uri->getScheme() || + $this->authority !== $uri->getAuthority() || + $uri->isRelativePath()) { + return $uri; + } + + $targetPath = $uri->getPath(); + $basePath = $this->path; + + $uri = $uri + ->withScheme(null) + ->withUserInfo(null) + ->withPort(null) + ->withHost(null); + + return match (true) { + $targetPath !== $basePath => $uri->withPath(self::relativizePath($targetPath, $basePath)), + $this->query === $uri->getQuery() => $uri->withPath('')->withQuery(null), + null === $uri->getQuery() => $uri->withPath(self::formatPathWithEmptyBaseQuery($targetPath)), + default => $uri->withPath(''), + }; + } + + /** + * Formatting the path to keep a resolvable URI. + */ + private static function formatPathWithEmptyBaseQuery(string $path): string + { + $targetSegments = self::getSegments($path); + $basename = $targetSegments[array_key_last($targetSegments)]; + + return '' === $basename ? './' : $basename; + } + + /** + * Relatives the URI for an authority-less target URI. + */ + private static function relativizePath(string $path, string $basePath): string + { + $baseSegments = self::getSegments($basePath); + $targetSegments = self::getSegments($path); + $targetBasename = array_pop($targetSegments); + array_pop($baseSegments); + foreach ($baseSegments as $offset => $segment) { + if (!isset($targetSegments[$offset]) || $segment !== $targetSegments[$offset]) { + break; + } + unset($baseSegments[$offset], $targetSegments[$offset]); + } + $targetSegments[] = $targetBasename; + + return static::formatRelativePath( + str_repeat('../', count($baseSegments)).implode('/', $targetSegments), + $basePath + ); + } + + /** + * Formatting the path to keep a valid URI. + */ + private static function formatRelativePath(string $path, string $basePath): string + { + $colonPosition = strpos($path, ':'); + $slashPosition = strpos($path, '/'); + + return match (true) { + '' === $path => match (true) { + '' === $basePath, + '/' === $basePath => $basePath, + default => './', + }, + false === $colonPosition => $path, + false === $slashPosition, + $colonPosition < $slashPosition => "./$path", + default => $path, + }; + } + + /** + * returns the path segments. + * + * @return array + */ + private static function getSegments(string $path): array + { + return explode('/', match (true) { + '' === $path, + '/' !== $path[0] => $path, + default => substr($path, 1), + }); + } + + /** + * @return ComponentMap + */ + public function __debugInfo(): array + { + return $this->toComponents(); + } + + /** + * DEPRECATION WARNING! This method will be removed in the next major point release. + * + * @deprecated Since version 7.6.0 + * @codeCoverageIgnore + * @see Uri::parse() + * + * Creates a new instance from a URI and a Base URI. + * + * The returned URI must be absolute. + */ + #[Deprecated(message:'use League\Uri\Uri::parse() instead', since:'league/uri:7.6.0')] + public static function fromBaseUri(WhatWgUrl|Rfc3986Uri|Stringable|string $uri, WhatWgUrl|Rfc3986Uri|Stringable|string|null $baseUri = null): self + { + if ($uri instanceof Rfc3986Uri) { + $uri = $uri->toRawString(); + } + + if ($uri instanceof WhatWgUrl) { + $uri = $uri->toAsciiString(); + } + + if ($baseUri instanceof Rfc3986Uri) { + $baseUri = $baseUri->toRawString(); + } + + if ($baseUri instanceof WhatWgUrl) { + $baseUri = $baseUri->toAsciiString(); + } + + return self::new(UriString::resolve($uri, $baseUri)); + } + /** * DEPRECATION WARNING! This method will be removed in the next major point release. * @@ -1262,7 +1934,7 @@ final class Uri implements UriInterface public static function createFromBaseUri( Stringable|UriInterface|String $uri, Stringable|UriInterface|String|null $baseUri = null - ): UriInterface { + ): static { return self::fromBaseUri($uri, $baseUri); } diff --git a/vendor/league/uri/UriScheme.php b/vendor/league/uri/UriScheme.php new file mode 100644 index 000000000..03b48e5f0 --- /dev/null +++ b/vendor/league/uri/UriScheme.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri; + +use ValueError; + +/* + * Supported schemes and corresponding default port. + * + * @see https://github.com/python-hyper/hyperlink/blob/master/src/hyperlink/_url.py for the curating list definition + * @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml + * @see https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml + */ +enum UriScheme: string +{ + case About = 'about'; + case Acap = 'acap'; + case Bitcoin = 'bitcoin'; + case Geo = 'geo'; + case Blob = 'blob'; + case Afp = 'afp'; + case Data = 'data'; + case Dict = 'dict'; + case Dns = 'dns'; + case File = 'file'; + case Ftp = 'ftp'; + case Git = 'git'; + case Gopher = 'gopher'; + case Http = 'http'; + case Https = 'https'; + case Imap = 'imap'; + case Imaps = 'imaps'; + case Ipp = 'ipp'; + case Ipps = 'ipps'; + case Irc = 'irc'; + case Ircs = 'ircs'; + case Javascript = 'javascript'; + case Ldap = 'ldap'; + case Ldaps = 'ldaps'; + case Magnet = 'magnet'; + case Mailto = 'mailto'; + case Mms = 'mms'; + case Msrp = 'msrp'; + case Msrps = 'msrps'; + case Mtqp = 'mtqp'; + case News = 'news'; + case Nfs = 'nfs'; + case Nntp = 'nntp'; + case Nntps = 'nntps'; + case Pkcs11 = 'pkcs11'; + case Pop = 'pop'; + case Prospero = 'prospero'; + case Redis = 'redis'; + case Rsync = 'rsync'; + case Rtsp = 'rtsp'; + case Rtsps = 'rtsps'; + case Rtspu = 'rtspu'; + case Sftp = 'sftp'; + case Wss = 'wss'; + case Ws = 'ws'; + case Sip = 'sip'; + case Sips = 'sips'; + case Smb = 'smb'; + case Smtp = 'smtp'; + case Snmp = 'snmp'; + case Ssh = 'ssh'; + case Steam = 'steam'; + case Svn = 'svn'; + case Tel = 'tel'; + case Telnet = 'telnet'; + case Tn3270 = 'tn3270'; + case Urn = 'urn'; + case Ventrilo = 'ventrilo'; + case Vnc = 'vnc'; + case Wais = 'wais'; + case Xmpp = 'xmpp'; + + public function port(): ?int + { + return match ($this) { + self::Acap => 674, + self::Afp => 548, + self::Dict => 2628, + self::Dns => 53, + self::Ftp => 21, + self::Http, self::Ws => 80, + self::Https, self::Wss => 443, + self::Git => 9418, + self::Gopher => 70, + self::Imap => 143, + self::Imaps => 993, + self::Ipp, self::Ipps => 631, + self::Irc => 194, + self::Ircs => 6697, + self::Ldap => 389, + self::Ldaps => 636, + self::Mms => 1755, + self::Msrp, self::Msrps => 2855, + self::Mtqp => 1038, + self::Nfs => 111, + self::Nntp => 119, + self::Nntps => 563, + self::Pop => 110, + self::Prospero => 1525, + self::Redis => 6379, + self::Rsync => 873, + self::Rtsp => 554, + self::Rtsps => 322, + self::Rtspu => 5005, + self::Sftp, self::Ssh => 22, + self::Smb => 445, + self::Smtp => 25, + self::Snmp => 161, + self::Svn => 3690, + self::Telnet, self::Tn3270 => 23, + self::Ventrilo => 3784, + self::Vnc => 5900, + self::Wais => 210, + self::Xmpp => 80, + default => null, + }; + } + + public function type(): SchemeType + { + return match ($this) { + self::Urn, + self::About, + self::Bitcoin, + self::Blob, + self::Data, + self::Geo, + self::Javascript, + self::Magnet, + self::Mailto, + self::Pkcs11, + self::Sip, + self::Sips, + self::Tel => SchemeType::Opaque, + self::File => SchemeType::Hierarchical, + self::News => SchemeType::Unknown, + default => match (true) { + null !== $this->port() => SchemeType::Hierarchical, + default => SchemeType::Unknown, + }, + }; + } + + public function isWhatWgSpecial(): bool + { + return match ($this) { + self::Ftp, + self::Http, + self::Https, + self::Ws, + self::Wss => true, + default => false, + }; + } + + /** + * @return list + */ + public static function fromPort(?int $port): array + { + null === $port || 0 <= $port || throw new ValueError('The submitted port cannot be negative.'); + + static $reverse = []; + if ([] === $reverse) { + foreach (self::cases() as $case) { + $defaultPort = $case->port(); + if (null === $defaultPort) { + continue; + } + $reverse[$defaultPort] ??= []; + $reverse[$defaultPort][] = $case; + + } + } + + return $reverse[$port] ?? []; + } +} diff --git a/vendor/league/uri/UriTemplate.php b/vendor/league/uri/UriTemplate.php index 882a7b51a..934030d9c 100644 --- a/vendor/league/uri/UriTemplate.php +++ b/vendor/league/uri/UriTemplate.php @@ -13,16 +13,25 @@ declare(strict_types=1); namespace League\Uri; +use Deprecated; use League\Uri\Contracts\UriException; use League\Uri\Contracts\UriInterface; +use League\Uri\Exceptions\MissingFeature; use League\Uri\Exceptions\SyntaxError; use League\Uri\UriTemplate\Template; use League\Uri\UriTemplate\TemplateCanNotBeExpanded; use League\Uri\UriTemplate\VariableBag; +use Psr\Http\Message\UriFactoryInterface; +use Psr\Http\Message\UriInterface as Psr7UriInterface; use Stringable; +use Uri\InvalidUriException; +use Uri\Rfc3986\Uri as Rfc3986Uri; +use Uri\WhatWg\InvalidUrlException; +use Uri\WhatWg\Url as WhatWgUrl; use function array_fill_keys; use function array_key_exists; +use function class_exists; /** * Defines the URI Template syntax and the process for expanding a URI Template into a URI reference. @@ -31,8 +40,10 @@ use function array_key_exists; * @package League\Uri * @author Ignace Nyamagana Butera * @since 6.1.0 + * + * @phpstan-import-type InputValue from VariableBag */ -final class UriTemplate +final class UriTemplate implements Stringable { private readonly Template $template; private readonly VariableBag $defaultVariables; @@ -60,12 +71,17 @@ final class UriTemplate )); } - public function getTemplate(): string + /** + * Returns the string representation of the UriTemplate. + */ + public function __toString(): string { return $this->template->value; } /** + * Returns the distinct variables placeholders used in the template. + * * @return array */ public function getVariableNames(): array @@ -73,6 +89,9 @@ final class UriTemplate return $this->template->variableNames; } + /** + * @return array + */ public function getDefaultVariables(): array { return iterator_to_array($this->defaultVariables); @@ -92,32 +111,179 @@ final class UriTemplate public function withDefaultVariables(iterable $defaultVariables): self { $defaultVariables = $this->filterVariables($defaultVariables); - if ($defaultVariables == $this->defaultVariables) { + if ($this->defaultVariables->equals($defaultVariables)) { return $this; } return new self($this->template, $defaultVariables); } + private function templateExpanded(iterable $variables = []): string + { + return $this->template->expand($this->filterVariables($variables)->replace($this->defaultVariables)); + } + + private function templateExpandedOrFail(iterable $variables = []): string + { + return $this->template->expandOrFail($this->filterVariables($variables)->replace($this->defaultVariables)); + } + /** * @throws TemplateCanNotBeExpanded if the variables are invalid * @throws UriException if the resulting expansion cannot be converted to a UriInterface instance */ - public function expand(iterable $variables = []): UriInterface + public function expand(iterable $variables = [], Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUri = null): UriInterface { - return Uri::new($this->template->expand( - $this->filterVariables($variables)->replace($this->defaultVariables) - )); + $expanded = $this->templateExpanded($variables); + + return null === $baseUri ? Uri::new($expanded) : (Uri::parse($expanded, $baseUri) ?? throw new SyntaxError('Unable to expand URI')); + } + + /** + * @throws MissingFeature if no Uri\Rfc3986\Uri class is found + * @throws TemplateCanNotBeExpanded if the variables are invalid + * @throws InvalidUriException if the base URI cannot be converted to a Uri\Rfc3986\Uri instance + * @throws InvalidUriException if the resulting expansion cannot be converted to a Uri\Rfc3986\Uri instance + */ + public function expandToUri(iterable $variables = [], Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUri = null): Rfc3986Uri + { + class_exists(Rfc3986Uri::class) || throw new MissingFeature('Support for '.Rfc3986Uri::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you owm polyfill.'); + + return new Rfc3986Uri($this->templateExpanded($variables), $this->newRfc3986Uri($baseUri)); + } + + /** + * @throws MissingFeature if no Uri\Whatwg\Url class is found + * @throws TemplateCanNotBeExpanded if the variables are invalid + * @throws InvalidUrlException if the base URI cannot be converted to a Uri\Whatwg\Url instance + * @throws InvalidUrlException if the resulting expansion cannot be converted to a Uri\Whatwg\Url instance + */ + public function expandToUrl(iterable $variables = [], Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUrl = null): WhatWgUrl + { + class_exists(WhatWgUrl::class) || throw new MissingFeature('Support for '.WhatWgUrl::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you owm polyfill.'); + + return new WhatWgUrl($this->templateExpanded($variables), $this->newWhatWgUrl($baseUrl)); + } + + /** + * @throws TemplateCanNotBeExpanded if the variables are invalid + * @throws UriException if the resulting expansion cannot be converted to a UriInterface instance + */ + public function expandToPsr7Uri( + iterable $variables = [], + Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUrl = null, + UriFactoryInterface $uriFactory = new HttpFactory() + ): Psr7UriInterface { + $uriString = $this->templateExpandedOrFail($variables); + + return $uriFactory->createUri( + null === $baseUrl + ? $uriString + : UriString::resolve($uriString, match (true) { + $baseUrl instanceof Rfc3986Uri => $baseUrl->toRawString(), + $baseUrl instanceof WhatWgUrl => $baseUrl->toUnicodeString(), + default => $baseUrl, + }) + ); } /** * @throws TemplateCanNotBeExpanded if the variables are invalid or missing * @throws UriException if the resulting expansion cannot be converted to a UriInterface instance */ - public function expandOrFail(iterable $variables = []): UriInterface + public function expandOrFail(iterable $variables = [], Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUri = null): UriInterface { - return Uri::new($this->template->expandOrFail( - $this->filterVariables($variables)->replace($this->defaultVariables) - )); + $expanded = $this->templateExpandedOrFail($variables); + + return null === $baseUri ? Uri::new($expanded) : (Uri::parse($expanded, $baseUri) ?? throw new SyntaxError('Unable to expand URI')); + } + + /** + * @throws MissingFeature if no Uri\Rfc3986\Uri class is found + * @throws TemplateCanNotBeExpanded if the variables are invalid + * @throws InvalidUriException if the base URI cannot be converted to a Uri\Rfc3986\Uri instance + * @throws InvalidUriException if the resulting expansion cannot be converted to a Uri\Rfc3986\Uri instance + */ + public function expandToUriOrFail(iterable $variables = [], Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUri = null): Rfc3986Uri + { + class_exists(Rfc3986Uri::class) || throw new MissingFeature('Support for '.Rfc3986Uri::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you owm polyfill.'); + + return new Rfc3986Uri($this->templateExpandedOrFail($variables), $this->newRfc3986Uri($baseUri)); + } + + /** + * @throws MissingFeature if no Uri\Whatwg\Url class is found + * @throws TemplateCanNotBeExpanded if the variables are invalid + * @throws InvalidUrlException if the base URI cannot be converted to a Uri\Whatwg\Url instance + * @throws InvalidUrlException if the resulting expansion cannot be converted to a Uri\Whatwg\Url instance + */ + public function expandToUrlOrFail(iterable $variables = [], Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUrl = null): WhatWgUrl + { + class_exists(WhatWgUrl::class) || throw new MissingFeature('Support for '.WhatWgUrl::class.' requires PHP8.5+ or a polyfill. Run "composer require league/uri-polyfill" or use you owm polyfill.'); + + return new WhatWgUrl($this->templateExpandedOrFail($variables), $this->newWhatWgUrl($baseUrl)); + } + + /** + * @throws TemplateCanNotBeExpanded if the variables are invalid + * @throws UriException if the resulting expansion cannot be converted to a UriInterface instance + */ + public function expandToPsr7UriOrFail( + iterable $variables = [], + Rfc3986Uri|WhatWgUrl|Stringable|string|null $baseUrl = null, + UriFactoryInterface $uriFactory = new HttpFactory() + ): Psr7UriInterface { + $uriString = $this->templateExpandedOrFail($variables); + + return $uriFactory->createUri( + null === $baseUrl + ? $uriString + : UriString::resolve($uriString, match (true) { + $baseUrl instanceof Rfc3986Uri => $baseUrl->toRawString(), + $baseUrl instanceof WhatWgUrl => $baseUrl->toUnicodeString(), + default => $baseUrl, + }) + ); + } + + /** + * @throws InvalidUrlException + */ + private function newWhatWgUrl(Rfc3986Uri|WhatWgUrl|Stringable|string|null $url = null): ?WhatWgUrl + { + return match (true) { + null === $url => null, + $url instanceof WhatWgUrl => $url, + $url instanceof Rfc3986Uri => new WhatWgUrl($url->toRawString()), + default => new WhatWgUrl((string) $url), + }; + } + + /** + * @throws InvalidUriException + */ + private function newRfc3986Uri(Rfc3986Uri|WhatWgUrl|Stringable|string|null $uri = null): ?Rfc3986Uri + { + return match (true) { + null === $uri => null, + $uri instanceof Rfc3986Uri => $uri, + $uri instanceof WhatWgUrl => new Rfc3986Uri($uri->toAsciiString()), + default => new Rfc3986Uri((string) $uri), + }; + } + + /** + * DEPRECATION WARNING! This method will be removed in the next major point release. + * + * @deprecated Since version 7.6.0 + * @codeCoverageIgnore + * @see UriTemplate::toString() + * + * Create a new instance from the environment. + */ + #[Deprecated(message:'use League\Uri\UriTemplate::__toString() instead', since:'league/uri:7.6.0')] + public function getTemplate(): string + { + return $this->__toString(); } } diff --git a/vendor/league/uri/UriTemplate/Template.php b/vendor/league/uri/UriTemplate/Template.php index 2727c9480..378e96a00 100644 --- a/vendor/league/uri/UriTemplate/Template.php +++ b/vendor/league/uri/UriTemplate/Template.php @@ -23,8 +23,8 @@ use function array_reduce; use function array_unique; use function preg_match_all; use function preg_replace; -use function str_contains; use function str_replace; +use function strpbrk; use const PREG_SET_ORDER; @@ -65,9 +65,7 @@ final class Template implements Stringable $template = (string) $template; /** @var string $remainder */ $remainder = preg_replace(self::REGEXP_EXPRESSION_DETECTOR, '', $template); - if (str_contains($remainder, '{') || str_contains($remainder, '}')) { - throw new SyntaxError('The template "'.$template.'" contains invalid expressions.'); - } + false === strpbrk($remainder, '{}') || throw new SyntaxError('The template "'.$template.'" contains invalid expressions.'); preg_match_all(self::REGEXP_EXPRESSION_DETECTOR, $template, $founds, PREG_SET_ORDER); diff --git a/vendor/league/uri/UriTemplate/VarSpecifier.php b/vendor/league/uri/UriTemplate/VarSpecifier.php index 1730d3ca1..9d6773f30 100644 --- a/vendor/league/uri/UriTemplate/VarSpecifier.php +++ b/vendor/league/uri/UriTemplate/VarSpecifier.php @@ -41,10 +41,7 @@ final class VarSpecifier public static function new(string $specification): self { - if (1 !== preg_match(self::REGEXP_VARSPEC, $specification, $parsed)) { - throw new SyntaxError('The variable specification "'.$specification.'" is invalid.'); - } - + 1 === preg_match(self::REGEXP_VARSPEC, $specification, $parsed) || throw new SyntaxError('The variable specification "'.$specification.'" is invalid.'); $properties = ['name' => $parsed['name'], 'modifier' => $parsed['modifier'] ?? '', 'position' => $parsed['position'] ?? '']; if ('' !== $properties['position']) { diff --git a/vendor/league/uri/UriTemplate/VariableBag.php b/vendor/league/uri/UriTemplate/VariableBag.php index cf6d08f30..1928fd3c7 100644 --- a/vendor/league/uri/UriTemplate/VariableBag.php +++ b/vendor/league/uri/UriTemplate/VariableBag.php @@ -97,6 +97,12 @@ final class VariableBag implements ArrayAccess, Countable, IteratorAggregate return [] !== $this->variables; } + public function equals(mixed $value): bool + { + return $value instanceof self + && $this->variables === $value->variables; + } + /** * Fetches the variable value if none found returns null. * diff --git a/vendor/league/uri/Urn.php b/vendor/league/uri/Urn.php new file mode 100644 index 000000000..83c4b568f --- /dev/null +++ b/vendor/league/uri/Urn.php @@ -0,0 +1,578 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri; + +use Closure; +use JsonSerializable; +use League\Uri\Contracts\Conditionable; +use League\Uri\Contracts\UriComponentInterface; +use League\Uri\Contracts\UriInterface; +use League\Uri\Exceptions\SyntaxError; +use League\Uri\UriTemplate\Template; +use Stringable; +use Uri\Rfc3986\Uri as Rfc3986Uri; +use Uri\WhatWg\Url as WhatWgUrl; + +use function is_bool; +use function preg_match; +use function str_replace; +use function strtolower; + +/** + * @phpstan-type UrnSerialize array{0: array{urn: non-empty-string}, 1: array{}} + * @phpstan-import-type InputComponentMap from UriString + * @phpstan-type UrnMap array{ + * scheme: 'urn', + * nid: string, + * nss: string, + * r_component: ?string, + * q_component: ?string, + * f_component: ?string, + * } + */ +final class Urn implements Conditionable, Stringable, JsonSerializable +{ + /** + * RFC8141 regular expression URN splitter. + * + * The regexp does not perform any look-ahead. + * Not all invalid URN are caught. Some + * post-regexp-validation checks + * are mandatory. + * + * @link https://datatracker.ietf.org/doc/html/rfc8141#section-2 + * + * @var string + */ + private const REGEXP_URN_PARTS = '/^ + urn: + (?[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?): # NID + (?.*?) # NSS + (?\?\+(?.*?))? # r-component + (?\?\=(?.*?))? # q-component + (?:\#(?.*))? # f-component + $/xi'; + + /** + * RFC8141 namespace identifier regular expression. + * + * @link https://datatracker.ietf.org/doc/html/rfc8141#section-2 + * + * @var string + */ + private const REGEX_NID_SEQUENCE = '/^[a-z0-9]([a-z0-9-]{0,30})[a-z0-9]$/xi'; + + /** @var non-empty-string */ + private readonly string $uriString; + /** @var non-empty-string */ + private readonly string $nid; + /** @var non-empty-string */ + private readonly string $nss; + /** @var non-empty-string|null */ + private readonly ?string $rComponent; + /** @var non-empty-string|null */ + private readonly ?string $qComponent; + /** @var non-empty-string|null */ + private readonly ?string $fComponent; + + /** + * @param Rfc3986Uri|WhatWgUrl|Stringable|string $urn the percent-encoded URN + */ + public static function parse(Rfc3986Uri|WhatWgUrl|Stringable|string $urn): ?Urn + { + try { + return self::fromString($urn); + } catch (SyntaxError) { + return null; + } + } + + /** + * @param Rfc3986Uri|WhatWgUrl|Stringable|string $urn the percent-encoded URN + * @see self::fromString() + * + * @throws SyntaxError if the URN is invalid + */ + public static function new(Rfc3986Uri|WhatWgUrl|Stringable|string $urn): self + { + return self::fromString($urn); + } + + /** + * @param Rfc3986Uri|WhatWgUrl|Stringable|string $urn the percent-encoded URN + * + * @throws SyntaxError if the URN is invalid + */ + public static function fromString(Rfc3986Uri|WhatWgUrl|Stringable|string $urn): self + { + $urn = match (true) { + $urn instanceof Rfc3986Uri => $urn->toRawString(), + $urn instanceof WhatWgUrl => $urn->toAsciiString(), + default => (string) $urn, + }; + + UriString::containsRfc3986Chars($urn) || throw new SyntaxError('The URN is malformed, it contains invalid characters.'); + 1 === preg_match(self::REGEXP_URN_PARTS, $urn, $matches) || throw new SyntaxError('The URN string is invalid.'); + + return new self( + nid: $matches['nid'], + nss: $matches['nss'], + rComponent: (isset($matches['frc']) && '' !== $matches['frc']) ? $matches['rcomponent'] : null, + qComponent: (isset($matches['fqc']) && '' !== $matches['fqc']) ? $matches['qcomponent'] : null, + fComponent: $matches['fcomponent'] ?? null, + ); + } + + /** + * Create a new instance from a hash representation of the URI similar + * to PHP parse_url function result. + * + * @param InputComponentMap $components a hash representation of the URI similar to PHP parse_url function result + */ + public static function fromComponents(array $components = []): self + { + $components += [ + 'scheme' => null, 'user' => null, 'pass' => null, 'host' => null, + 'port' => null, 'path' => '', 'query' => null, 'fragment' => null, + ]; + + return self::fromString(UriString::build($components)); + } + + /** + * @param Stringable|string $nss the percent-encoded NSS + * + * @throws SyntaxError if the URN is invalid + */ + public static function fromRfc2141(Stringable|string $nid, Stringable|string $nss): self + { + return new self((string) $nid, (string) $nss); + } + + /** + * @param string $nss the percent-encoded NSS + * @param ?string $rComponent the percent-encoded r-component + * @param ?string $qComponent the percent-encoded q-component + * @param ?string $fComponent the percent-encoded f-component + * + * @throws SyntaxError if one of the URN part is invalid + */ + private function __construct( + string $nid, + string $nss, + ?string $rComponent = null, + ?string $qComponent = null, + ?string $fComponent = null, + ) { + ('' !== $nid && 1 === preg_match(self::REGEX_NID_SEQUENCE, $nid)) || throw new SyntaxError('The URN is malformed, the NID is invalid.'); + ('' !== $nss && Encoder::isPathEncoded($nss)) || throw new SyntaxError('The URN is malformed, the NSS is invalid.'); + + /** @param Closure(string): ?non-empty-string $closure */ + $validateComponent = static fn (?string $value, Closure $closure, string $name): ?string => match (true) { + null === $value, + ('' !== $value && 1 !== preg_match('/[#?]/', $value) && $closure($value)) => $value, + default => throw new SyntaxError('The URN is malformed, the `'.$name.'` component is invalid.'), + }; + + $this->nid = $nid; + $this->nss = $nss; + $this->rComponent = $validateComponent($rComponent, Encoder::isPathEncoded(...), 'r-component'); + $this->qComponent = $validateComponent($qComponent, Encoder::isQueryEncoded(...), 'q-component'); + $this->fComponent = $validateComponent($fComponent, Encoder::isFragmentEncoded(...), 'f-component'); + $this->uriString = $this->setUriString(); + } + + /** + * @return non-empty-string + */ + private function setUriString(): string + { + $str = $this->toRfc2141(); + if (null !== $this->rComponent) { + $str .= '?+'.$this->rComponent; + } + + if (null !== $this->qComponent) { + $str .= '?='.$this->qComponent; + } + + if (null !== $this->fComponent) { + $str .= '#'.$this->fComponent; + } + + return $str; + } + + /** + * Returns the NID. + * + * @return non-empty-string + */ + public function getNid(): string + { + return $this->nid; + } + + /** + * Returns the percent-encoded NSS. + * + * @return non-empty-string + */ + public function getNss(): string + { + return $this->nss; + } + + /** + * Returns the percent-encoded r-component string or null if it is not set. + * + * @return ?non-empty-string + */ + public function getRComponent(): ?string + { + return $this->rComponent; + } + + /** + * Returns the percent-encoded q-component string or null if it is not set. + * + * @return ?non-empty-string + */ + public function getQComponent(): ?string + { + return $this->qComponent; + } + + /** + * Returns the percent-encoded f-component string or null if it is not set. + * + * @return ?non-empty-string + */ + public function getFComponent(): ?string + { + return $this->fComponent; + } + + /** + * Returns the RFC8141 URN string representation. + * + * @return non-empty-string + */ + public function toString(): string + { + return $this->uriString; + } + + /** + * Returns the RFC2141 URN string representation. + * + * @return non-empty-string + */ + public function toRfc2141(): string + { + return 'urn:'.$this->nid.':'.$this->nss; + } + + /** + * Returns the human-readable string representation of the URN as an IRI. + * + * @see https://datatracker.ietf.org/doc/html/rfc3987 + */ + public function toDisplayString(): string + { + return UriString::toIriString($this->uriString); + } + + /** + * Returns the RFC8141 URN string representation. + * + * @see self::toString() + * + * @return non-empty-string + */ + public function __toString(): string + { + return $this->toString(); + } + + /** + * Returns the RFC8141 URN string representation. + * @see self::toString() + * + * @return non-empty-string + */ + public function jsonSerialize(): string + { + return $this->toString(); + } + + /** + * Returns the RFC3986 representation of the current URN. + * + * If a template URI is used the following variables as present + * {nid} for the namespace identifier + * {nss} for the namespace specific string + * {r_component} for the r-component without its delimiter + * {q_component} for the q-component without its delimiter + * {f_component} for the f-component without its delimiter + */ + public function resolve(UriTemplate|Template|string|null $template = null): UriInterface + { + return null !== $template ? Uri::fromTemplate($template, $this->toComponents()) : Uri::new($this->uriString); + } + + public function hasRComponent(): bool + { + return null !== $this->rComponent; + } + + public function hasQComponent(): bool + { + return null !== $this->qComponent; + } + + public function hasFComponent(): bool + { + return null !== $this->fComponent; + } + + public function hasOptionalComponent(): bool + { + return null !== $this->rComponent + || null !== $this->qComponent + || null !== $this->fComponent; + } + + /** + * Return an instance with the specified NID. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified NID. + * + * @throws SyntaxError for invalid component or transformations + * that would result in an object in invalid state. + */ + public function withNid(Stringable|string $nid): self + { + $nid = (string) $nid; + + return $this->nid === $nid ? $this : new self( + nid: $nid, + nss: $this->nss, + rComponent: $this->rComponent, + qComponent: $this->qComponent, + fComponent: $this->fComponent, + ); + } + + /** + * Return an instance with the specified NSS. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified NSS. + * + * @throws SyntaxError for invalid component or transformations + * that would result in an object in invalid state. + */ + public function withNss(Stringable|string $nss): self + { + $nss = Encoder::encodePath($nss); + + return $this->nss === $nss ? $this : new self( + nid: $this->nid, + nss: $nss, + rComponent: $this->rComponent, + qComponent: $this->qComponent, + fComponent: $this->fComponent, + ); + } + + /** + * Return an instance with the specified r-component. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified r-component. + * + * The component is removed if the value is null. + * + * @throws SyntaxError for invalid component or transformations + * that would result in an object in invalid state. + */ + public function withRComponent(Stringable|string|null $component): self + { + if ($component instanceof UriComponentInterface) { + $component = $component->value(); + } + + if (null !== $component) { + $component = self::formatComponent(Encoder::encodePath($component)); + } + + return $this->rComponent === $component ? $this : new self( + nid: $this->nid, + nss: $this->nss, + rComponent: $component, + qComponent: $this->qComponent, + fComponent: $this->fComponent, + ); + } + + private static function formatComponent(?string $component): ?string + { + return null === $component ? null : str_replace(['?', '#'], ['%3F', '%23'], $component); + } + + /** + * Return an instance with the specified q-component. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified q-component. + * + * The component is removed if the value is null. + * + * @throws SyntaxError for invalid component or transformations + * that would result in an object in invalid state. + */ + public function withQComponent(Stringable|string|null $component): self + { + if ($component instanceof UriComponentInterface) { + $component = $component->value(); + } + + $component = self::formatComponent(Encoder::encodeQueryOrFragment($component)); + + return $this->qComponent === $component ? $this : new self( + nid: $this->nid, + nss: $this->nss, + rComponent: $this->rComponent, + qComponent: $component, + fComponent: $this->fComponent, + ); + } + + /** + * Return an instance with the specified f-component. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified f-component. + * + * The component is removed if the value is null. + * + * @throws SyntaxError for invalid component or transformations + * that would result in an object in invalid state. + */ + public function withFComponent(Stringable|string|null $component): self + { + if ($component instanceof UriComponentInterface) { + $component = $component->value(); + } + + $component = self::formatComponent(Encoder::encodeQueryOrFragment($component)); + + return $this->fComponent === $component ? $this : new self( + nid: $this->nid, + nss: $this->nss, + rComponent: $this->rComponent, + qComponent: $this->qComponent, + fComponent: $component, + ); + } + + public function normalize(): self + { + $copy = new self( + nid: strtolower($this->nid), + nss: (string) Encoder::normalizePath($this->nss), + rComponent: null === $this->rComponent ? $this->rComponent : Encoder::normalizePath($this->rComponent), + qComponent: Encoder::normalizeQuery($this->qComponent), + fComponent: Encoder::normalizeFragment($this->fComponent), + ); + + return $copy->uriString === $this->uriString ? $this : $copy; + } + + public function equals(Urn|Rfc3986Uri|WhatWgUrl|Stringable|string $other, UrnComparisonMode $urnComparisonMode = UrnComparisonMode::ExcludeComponents): bool + { + if (!$other instanceof Urn) { + $other = self::parse($other); + } + + return (null !== $other) && match ($urnComparisonMode) { + UrnComparisonMode::ExcludeComponents => $other->normalize()->toRfc2141() === $this->normalize()->toRfc2141(), + UrnComparisonMode::IncludeComponents => $other->normalize()->toString() === $this->normalize()->toString(), + }; + } + + public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static + { + if (!is_bool($condition)) { + $condition = $condition($this); + } + + return match (true) { + $condition => $onSuccess($this), + null !== $onFail => $onFail($this), + default => $this, + } ?? $this; + } + + /** + * @return UrnSerialize + */ + public function __serialize(): array + { + return [['urn' => $this->toString()], []]; + } + + /** + * @param UrnSerialize $data + * + * @throws SyntaxError + */ + public function __unserialize(array $data): void + { + [$properties] = $data; + $uri = self::fromString($properties['urn'] ?? throw new SyntaxError('The `urn` property is missing from the serialized object.')); + + $this->nid = $uri->nid; + $this->nss = $uri->nss; + $this->rComponent = $uri->rComponent; + $this->qComponent = $uri->qComponent; + $this->fComponent = $uri->fComponent; + $this->uriString = $uri->uriString; + } + + /** + * @return UrnMap + */ + public function toComponents(): array + { + return [ + 'scheme' => 'urn', + 'nid' => $this->nid, + 'nss' => $this->nss, + 'r_component' => $this->rComponent, + 'q_component' => $this->qComponent, + 'f_component' => $this->fComponent, + ]; + } + + /** + * @return UrnMap + */ + public function __debugInfo(): array + { + return $this->toComponents(); + } +} diff --git a/vendor/league/uri/composer.json b/vendor/league/uri/composer.json index 942c73e70..df46c6c93 100644 --- a/vendor/league/uri/composer.json +++ b/vendor/league/uri/composer.json @@ -5,8 +5,12 @@ "keywords": [ "url", "uri", + "urn", + "uri-template", + "rfc2141", "rfc3986", "rfc3987", + "rfc8141", "rfc6570", "psr-7", "parse_url", @@ -20,8 +24,7 @@ "parse_str", "query-string", "querystring", - "hostname", - "uri-template" + "hostname" ], "license": "MIT", "homepage": "https://uri.thephpleague.com", @@ -45,7 +48,8 @@ ], "require": { "php": "^8.1", - "league/uri-interfaces": "^7.5" + "league/uri-interfaces": "^7.6", + "psr/http-factory": "^1" }, "autoload": { "psr-4": { @@ -57,13 +61,17 @@ }, "suggest": { "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", "league/uri-components" : "Needed to easily manipulate URI objects components", + "league/uri-polyfill" : "Needed to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present", + "rowbot/url": "to handle WHATWG URL" }, "extra": { "branch-alias": { diff --git a/vendor/macgirvin/http-message-signer/README.md b/vendor/macgirvin/http-message-signer/README.md index cd3dca089..6085a138f 100644 --- a/vendor/macgirvin/http-message-signer/README.md +++ b/vendor/macgirvin/http-message-signer/README.md @@ -17,18 +17,11 @@ Supports: - 'hmac-sha256' - 'ecdsa-p256-sha256' - 'ecdsa-p384-sha384' - -Requirements: -- bakame/http-structured-fields -- phpseclib/phpseclib -- psr/http-message -- paragonie/easy-ecc ## Note Please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+ - ## Installation ```bash @@ -166,6 +159,8 @@ To sign or verify an HTTP Response, use a ResponseInterface as the provided `$in ## Known issues Currently not implemented is the special handling of the `cookie` and `set-cookie` headers when using the `sf` modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC. +Currently, PEM keys are supported as per the RFC. JWT/JWK are not yet supported, and support of other key formats depends on the algorithm used. + Pull requests welcome. ## License diff --git a/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php b/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php index 447f31890..d54c21f07 100644 --- a/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php +++ b/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php @@ -11,9 +11,11 @@ use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use phpseclib\Crypt\RSA; +use phpseclib3\Crypt\PublicKeyLoader; use ParagonIE\EasyECC\EasyECC; use ParagonIE\EasyECC\ECDSA\{PublicKey, SecretKey}; + class HttpMessageSigner { private string $keyId; @@ -470,36 +472,93 @@ class HttpMessageSigner private function createSignature(string $data): string { $algorithm = $this->getAlgorithm(); - return match ($algorithm) { - 'rsa-v1_5-sha256' => $this->rsa256Sign($data), - 'rsa-v1_5-sha512' => $this->rsa512Sign($data), - 'rsa-pss-sha512' => $this->pss512Sign($data), - 'ed25519' => $this->ed25519Sign($data), - 'hmac-sha256' => base64_encode(hash_hmac('sha256', $data, $this->getPrivateKey(), true)), - 'ecdsa-p256-sha256' => $this->ecdsa256Sign($data), - 'ecdsa-p384-sha384' => $this->ecdsa384Sign($data), - default => throw new UnProcessableSignatureException("Unsupported algorithm: $algorithm") + + switch ($algorithm) { + case 'RS256': + case 'rsa-v1_5-sha256': + return $this->rsa256Sign($data); + case 'RS384': + case 'rsa-v1_5-sha384': + return $this->rsa384Sign($data); + case 'RS512': + case 'rsa-v1_5-sha512': + return $this->rsa512Sign($data); + case 'EdDSA': + case 'Ed25519': + case 'ed25519': + return $this->ed25519Sign($data); + case 'ES256': + case 'ecdsa-p256-sha256': + return $this->ecdsa256Sign($data); + case 'ES384': + case 'ecdsa-p384-sha384': + return $this->ecdsa384Sign($data); + case 'ES512': + case 'ecdsa-p512-sha512': + return $this->ecdsa512Sign($data); + case 'HS256': + case 'hmac-sha256': + return base64_encode(hash_hmac('sha256', $data, $this->getPrivateKey(), true)); + case 'HS384': + case 'hmac-sha384': + return base64_encode(hash_hmac('sha384', $data, $this->getPrivateKey(), true)); + case 'HS512': + case 'hmac-sha512': + return base64_encode(hash_hmac('sha512', $data, $this->getPrivateKey(), true)); + case 'rsa-pss-sha512': + return $this->pss512Sign($data); + default: + throw new UnProcessableSignatureException("Unsupported algorithm: $algorithm"); }; } private function verifySignature(string $data, string $signature, string $algorithm): bool { - return match ($algorithm) { - 'rsa-v1_5-sha256' => openssl_verify($data, $signature, $this->getPublicKey(), OPENSSL_ALGO_SHA256) === 1, - 'rsa-v1_5-sha512' => openssl_verify($data, $signature, $this->getPublicKey(), OPENSSL_ALGO_SHA512) === 1, - 'rsa-pss-sha512' => $this->pss512Verify($data, $signature), - 'ed25519' => openssl_verify($data, $signature, $this->getPublicKey(), 'Ed25519') === 1, - 'hmac-sha256' => hash_equals( - base64_encode(hash_hmac('sha256', $data, $this->getPrivateKey(), true)), + switch ($algorithm) { + case 'RS256': + case 'rsa-v1_5-sha256': + return openssl_verify($data, $signature, $this->getPublicKey(), OPENSSL_ALGO_SHA256) === 1; + case 'RS384': + case 'rsa-v1_5-sha384': + return openssl_verify($data, $signature, $this->getPublicKey(), OPENSSL_ALGO_SHA384) === 1; + case 'RS512': + case 'rsa-v1_5-sha512': + return openssl_verify($data, $signature, $this->getPublicKey(), OPENSSL_ALGO_SHA512) === 1; + case 'EdDSA': + case 'Ed25519': + case 'ed25519': + return $this->ed25519Verify($data, $signature); + case 'ES256': + case 'ecdsa-p256-sha256': + return $this->ecdsa256Verify($data, $signature); + case 'ES384': + case 'ecdsa-p384-sha384': + return $this->ecdsa384Verify($data, $signature); + case 'ES512': + case 'ecdsa-p512-sha512': + return $this->ecdsa512Verify($data, $signature); + case 'HS256': + case 'hmac-sha256': + return hash_equals(base64_encode(hash_hmac('sha256', $data, $this->getPrivateKey(), true)), base64_encode($signature) - ), - 'ecdsa-p256-sha256' => $this->ecdsa256Verify($data, $signature), - 'ecdsa-p384-sha384' => $this->ecdsa384Verify($data, $signature), - default => false - }; + ); + case 'HS384': + case 'hmac-sha384': + return hash_equals(base64_encode(hash_hmac('sha384', $data, $this->getPrivateKey(), true)), + base64_encode($signature) + ); + case 'HS512': + case 'hmac-sha512': + return hash_equals(base64_encode(hash_hmac('sha512', $data, $this->getPrivateKey(), true)), + base64_encode($signature) + ); + case 'rsa-pss-sha512': + return $this->pss512Verify($data, $signature); + default: + return false; + } } - /* sign with rsa or ed25519 */ private function rsa256Sign(string $data): string { @@ -508,6 +567,15 @@ class HttpMessageSigner } return base64_encode($signature); } + + private function rsa384Sign(string $data): string + { + if (!openssl_sign($data, $signature, $this->getPrivateKey(), OPENSSL_ALGO_SHA384)) { + throw new UnProcessableSignatureException("RSA signing failed"); + } + return base64_encode($signature); + } + private function rsa512Sign(string $data): string { if (!openssl_sign($data, $signature, $this->getPrivateKey(), OPENSSL_ALGO_SHA512)) { @@ -536,6 +604,16 @@ class HttpMessageSigner return base64_encode($signature); } + private function ecdsa512Sign(string $data): string + { + $ecc = new EasyECC('P512'); + $signature = $ecc->sign($data, SecretKey::importPem($this->getPrivateKey()), false); + if (!$signature) { + throw new UnprocessableSignatureException("ECDSA signing failed"); + } + return base64_encode($signature); + } + private function pss512Sign(string $data): string { $rsa = new RSA(); @@ -555,12 +633,30 @@ class HttpMessageSigner private function ed25519Sign(string $data): string { - if (!openssl_sign($data, $signature, $this->getPrivateKey(), "Ed25519")) { - throw new UnProcessableSignatureException("Ed25519 signing failed"); + try { + $private_key = PublicKeyLoader::loadPrivateKey($this->getPrivateKey()); + $signature = $private_key->sign($data); + + } catch (\Exception $e) { + $signature = ''; + throw new UnprocessableSignatureException($e->getMessage()); } return base64_encode($signature); } + private function ed25519Verify(string $data, $signature): bool + { + try { + $public_key = PublicKeyLoader::loadPublicKey($this->getPublicKey()); + $verified = $public_key->verify($data, $signature); + } + catch (\Exception $e) { + $verified = false; + throw new UnProcessableSignatureException($e->getMessage()); + } + return $verified; + } + private function ecdsa256Verify(string $data, string $signature): bool { $ecc = new EasyECC('P256'); @@ -585,6 +681,19 @@ class HttpMessageSigner return $verified; } + private function ecdsa512Verify(string $data, string $signature): bool + { + $ecc = new EasyECC('P512'); + try { + $verified = $ecc->verify($data, PublicKey::importPem($this->getPublicKey()), $signature, false); + } + catch (UnprocessableSignatureException $e) { + $verified = false; + } + return $verified; + } + + private function pss512Verify(string $data, $signature): bool { $rsa = new RSA(); @@ -602,6 +711,7 @@ class HttpMessageSigner return $verified; } + /* parse a structed dict */ private function parseStructuredDict(string $headerValue) diff --git a/vendor/scssphp/scssphp/composer.json b/vendor/scssphp/scssphp/composer.json index 599b58fd1..3a0287184 100644 --- a/vendor/scssphp/scssphp/composer.json +++ b/vendor/scssphp/scssphp/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "scssphp is a compiler for SCSS written in PHP.", "keywords": ["css", "stylesheet", "scss", "sass", "less"], - "homepage": "http://scssphp.github.io/scssphp/", + "homepage": "https://scssphp.github.io/scssphp/", "license": [ "MIT" ], @@ -29,24 +29,25 @@ "php": ">=8.1", "ext-ctype": "*", "ext-json": "*", - "league/uri": "^7.4", - "league/uri-interfaces": "^7.4", - "scssphp/source-span": "^1.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-mbstring": "^1.30" - }, - "suggest": { - "ext-mbstring": "For best performance, mbstring should be installed as it is faster than the polyfill" + "ext-mbstring": "*", + "league/uri": "^7.6", + "league/uri-interfaces": "^7.6", + "scssphp/source-span": "^1.1", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4", + "jgthms/bulma": "~0.9.4", + "jiripudil/phpstan-sealed-classes": "^1.3", + "phpstan/phpstan": "^2.1.31", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^9.5.6", "sass/sass-spec": "*", - "squizlabs/php_codesniffer": "~3.5", - "symfony/phpunit-bridge": "^5.1", - "symfony/var-dumper": "^6.3", + "squizlabs/php_codesniffer": "^3.13", + "symfony/phpunit-bridge": "^7.3 || ^8.0", + "symfony/polyfill-php84": "^1.33", + "symfony/var-dumper": "^6.4 || ^7.3 || ^8.0", "thoughtbot/bourbon": "^7.0", - "twbs/bootstrap": "~5.0", + "twbs/bootstrap": "^5.3", "twbs/bootstrap4": "4.6.1", "zurb/foundation": "~6.7.0" }, @@ -87,6 +88,24 @@ } } }, + { + "type": "package", + "package": { + "name": "jgthms/bulma", + "version": "v0.9.4", + "source": { + "type": "git", + "url": "https://github.com/jgthms/bulma.git", + "reference": "3e00a8e6d0d0e566d507328f0185ef84854effba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jgthms/bulma/zipball/3e00a8e6d0d0e566d507328f0185ef84854effba", + "reference": "3e00a8e6d0d0e566d507328f0185ef84854effba", + "shasum": "" + } + } + }, { "type": "package", "package": { @@ -107,15 +126,6 @@ } ], "config": { - "sort-packages": true, - "allow-plugins": { - "bamarni/composer-bin-plugin": true - } - }, - "extra": { - "bamarni-bin": { - "forward-command": false, - "bin-links": false - } + "sort-packages": true } } diff --git a/vendor/scssphp/scssphp/src/Ast/Css/MediaQuerySingletonMergeResult.php b/vendor/scssphp/scssphp/src/Ast/Css/MediaQuerySingletonMergeResult.php index 56ca7abf7..12b9d0b69 100644 --- a/vendor/scssphp/scssphp/src/Ast/Css/MediaQuerySingletonMergeResult.php +++ b/vendor/scssphp/scssphp/src/Ast/Css/MediaQuerySingletonMergeResult.php @@ -12,6 +12,9 @@ namespace ScssPhp\ScssPhp\Ast\Css; +/** + * @internal + */ enum MediaQuerySingletonMergeResult implements MediaQueryMergeResult { case empty; diff --git a/vendor/scssphp/scssphp/src/Ast/Css/ModifiableCssNode.php b/vendor/scssphp/scssphp/src/Ast/Css/ModifiableCssNode.php index 5d944682b..543bc2337 100644 --- a/vendor/scssphp/scssphp/src/Ast/Css/ModifiableCssNode.php +++ b/vendor/scssphp/scssphp/src/Ast/Css/ModifiableCssNode.php @@ -137,7 +137,7 @@ abstract class ModifiableCssNode implements CssNode } /** - * @@internal + * @internal */ protected function resetParentReferences(): void { diff --git a/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php b/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php index 4098f328f..905b8f947 100644 --- a/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php +++ b/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php @@ -13,6 +13,7 @@ namespace ScssPhp\ScssPhp\Ast\Sass; use League\Uri\Contracts\UriInterface; +use ScssPhp\ScssPhp\Exception\MultiSpanSassScriptException; use ScssPhp\ScssPhp\Exception\SassFormatException; use ScssPhp\ScssPhp\Exception\SassScriptException; use ScssPhp\ScssPhp\Logger\LoggerInterface; @@ -130,13 +131,13 @@ final class ArgumentDeclaration implements SassNode if ($i < $positional) { if (isset($names[$argument->getName()])) { $originalName = $this->originalArgumentName($argument->getName()); - throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.', $originalName)); + throw new SassScriptException(sprintf('Argument %s was passed both by position and by name.', $originalName)); } } elseif (isset($names[$argument->getName()])) { $nameUsed++; } elseif ($argument->getDefaultValue() === null) { $originalName = $this->originalArgumentName($argument->getName()); - throw new SassScriptException(sprintf('Missing argument $%s', $originalName)); + throw new MultiSpanSassScriptException(sprintf('Missing argument %s.', $originalName), 'invocation', ['declaration' => $this->getSpanWithName()]); } } @@ -153,7 +154,7 @@ final class ArgumentDeclaration implements SassNode $positional, StringUtil::pluralize('was', $positional, 'were') ); - throw new SassScriptException($message); + throw new MultiSpanSassScriptException($message, 'invocation', ['declaration' => $this->getSpanWithName()]); } if ($nameUsed < \count($names)) { @@ -164,7 +165,7 @@ final class ArgumentDeclaration implements SassNode StringUtil::pluralize('argument', \count($unknownNames)), StringUtil::toSentence(array_map(fn ($name) => '$' . $name, $unknownNames), 'or') ); - throw new SassScriptException($message); + throw new MultiSpanSassScriptException($message, 'invocation', ['declaration' => $this->getSpanWithName()]); } } diff --git a/vendor/scssphp/scssphp/src/Ast/Sass/CallableInvocation.php b/vendor/scssphp/scssphp/src/Ast/Sass/CallableInvocation.php index 94be58d9c..1e4798b0a 100644 --- a/vendor/scssphp/scssphp/src/Ast/Sass/CallableInvocation.php +++ b/vendor/scssphp/scssphp/src/Ast/Sass/CallableInvocation.php @@ -12,6 +12,9 @@ namespace ScssPhp\ScssPhp\Ast\Sass; +/** + * @internal + */ interface CallableInvocation extends SassNode { public function getArguments(): ArgumentInvocation; diff --git a/vendor/scssphp/scssphp/src/Ast/Sass/Interpolation.php b/vendor/scssphp/scssphp/src/Ast/Sass/Interpolation.php index cae238614..800d7af06 100644 --- a/vendor/scssphp/scssphp/src/Ast/Sass/Interpolation.php +++ b/vendor/scssphp/scssphp/src/Ast/Sass/Interpolation.php @@ -29,40 +29,13 @@ final class Interpolation implements SassNode private readonly FileSpan $span; - /** - * Creates a new {@see Interpolation} by concatenating a sequence of strings, - * {@see Expression}s, or nested {@see Interpolation}s. - * - * @param array $contents - */ - public static function concat(array $contents, FileSpan $span): Interpolation - { - $buffer = new InterpolationBuffer(); - - foreach ($contents as $element) { - if (\is_string($element)) { - $buffer->write($element); - } elseif ($element instanceof Expression) { - $buffer->add($element); - } elseif ($element instanceof Interpolation) { - $buffer->addInterpolation($element); - } else { - throw new \InvalidArgumentException(sprintf('The elements in $contents may only contains strings, Expressions, or Interpolations, "%s" given.', get_debug_type($element))); - } - } - - return $buffer->buildInterpolation($span); - } - /** * @param list $contents */ public function __construct(array $contents, FileSpan $span) { for ($i = 0; $i < \count($contents); $i++) { - if (!\is_string($contents[$i]) && !$contents[$i] instanceof Expression) { - throw new \TypeError('The contents of an Interpolation may only contain strings or Expression instances.'); - } + // Dart-sass has a validation on the type of elements here. This is useless for us because phpstan supports union types, unlike the Dart type system if ($i != 0 && \is_string($contents[$i]) && \is_string($contents[$i - 1])) { throw new \InvalidArgumentException('The contents of an Interpolation may not contain adjacent strings.'); diff --git a/vendor/scssphp/scssphp/src/Ast/Selector/SelectorList.php b/vendor/scssphp/scssphp/src/Ast/Selector/SelectorList.php index f08fd3e39..21a2585a5 100644 --- a/vendor/scssphp/scssphp/src/Ast/Selector/SelectorList.php +++ b/vendor/scssphp/scssphp/src/Ast/Selector/SelectorList.php @@ -14,6 +14,7 @@ namespace ScssPhp\ScssPhp\Ast\Selector; use League\Uri\Contracts\UriInterface; use ScssPhp\ScssPhp\Ast\Css\CssValue; +use ScssPhp\ScssPhp\Exception\MultiSpanSassException; use ScssPhp\ScssPhp\Exception\SassFormatException; use ScssPhp\ScssPhp\Exception\SassScriptException; use ScssPhp\ScssPhp\Exception\SimpleSassException; @@ -23,6 +24,7 @@ use ScssPhp\ScssPhp\Parser\InterpolationMap; use ScssPhp\ScssPhp\Parser\SelectorParser; use ScssPhp\ScssPhp\Util\EquatableUtil; use ScssPhp\ScssPhp\Util\ListUtil; +use ScssPhp\ScssPhp\Util\SpanUtil; use ScssPhp\ScssPhp\Value\ListSeparator; use ScssPhp\ScssPhp\Value\SassList; use ScssPhp\ScssPhp\Value\SassString; @@ -307,7 +309,7 @@ final class SelectorList extends Selector $lastComponent = $complex->getLastComponent(); if (\count($lastComponent->getCombinators()) !== 0) { - throw new SimpleSassException("Parent \"$complex\" is incompatible with this selector.", $parentSelector->getSpan()); + throw new MultiSpanSassException("Selector \"$complex\" can't be used as a parent in a compound selector.", SpanUtil::trimRight($lastComponent->getSpan()), 'outer selector', ['parent selector' => $parentSelector->getSpan()]); } $suffix = $parentSelector->getSuffix(); diff --git a/vendor/scssphp/scssphp/src/Exception/MultiSpanSassFormatException.php b/vendor/scssphp/scssphp/src/Exception/MultiSpanSassFormatException.php index 1c015fd62..b76ee5f76 100644 --- a/vendor/scssphp/scssphp/src/Exception/MultiSpanSassFormatException.php +++ b/vendor/scssphp/scssphp/src/Exception/MultiSpanSassFormatException.php @@ -14,6 +14,9 @@ namespace ScssPhp\ScssPhp\Exception; use SourceSpan\FileSpan; +/** + * @internal + */ final class MultiSpanSassFormatException extends MultiSpanSassException implements SassFormatException { public function withAdditionalSpan(FileSpan $span, string $label, ?\Throwable $previous = null): MultiSpanSassFormatException diff --git a/vendor/scssphp/scssphp/src/Exception/MultiSpanSassRuntimeException.php b/vendor/scssphp/scssphp/src/Exception/MultiSpanSassRuntimeException.php index 107a554df..ceb57004d 100644 --- a/vendor/scssphp/scssphp/src/Exception/MultiSpanSassRuntimeException.php +++ b/vendor/scssphp/scssphp/src/Exception/MultiSpanSassRuntimeException.php @@ -15,6 +15,9 @@ namespace ScssPhp\ScssPhp\Exception; use ScssPhp\ScssPhp\StackTrace\Trace; use SourceSpan\FileSpan; +/** + * @internal + */ final class MultiSpanSassRuntimeException extends MultiSpanSassException implements SassRuntimeException { private readonly Trace $sassTrace; diff --git a/vendor/scssphp/scssphp/src/Exception/SimpleSassFormatException.php b/vendor/scssphp/scssphp/src/Exception/SimpleSassFormatException.php index 07b1c9ae8..9a3e59cc7 100644 --- a/vendor/scssphp/scssphp/src/Exception/SimpleSassFormatException.php +++ b/vendor/scssphp/scssphp/src/Exception/SimpleSassFormatException.php @@ -14,6 +14,7 @@ namespace ScssPhp\ScssPhp\Exception; use ScssPhp\ScssPhp\StackTrace\Trace; use ScssPhp\ScssPhp\Util; +use ScssPhp\ScssPhp\Util\ErrorUtil; use SourceSpan\FileSpan; /** @@ -30,7 +31,7 @@ final class SimpleSassFormatException extends \Exception implements SassFormatEx $this->originalMessage = $message; $this->span = $span; - parent::__construct($span->message($message), 0, $previous); + parent::__construct(ErrorUtil::formatErrorMessage($message, $span, $this->getSassTrace()), 0, $previous); } /** diff --git a/vendor/scssphp/scssphp/src/Extend/ConcreteExtensionStore.php b/vendor/scssphp/scssphp/src/Extend/ConcreteExtensionStore.php index 5d267fde6..9c3a558a2 100644 --- a/vendor/scssphp/scssphp/src/Extend/ConcreteExtensionStore.php +++ b/vendor/scssphp/scssphp/src/Extend/ConcreteExtensionStore.php @@ -32,7 +32,10 @@ use ScssPhp\ScssPhp\Util\ListUtil; use ScssPhp\ScssPhp\Util\ModifiableBox; use SourceSpan\FileSpan; -class ConcreteExtensionStore implements ExtensionStore +/** + * @internal + */ +final class ConcreteExtensionStore implements ExtensionStore { /** * A map from all simple selectors in the stylesheet to the selector lists @@ -106,7 +109,7 @@ class ConcreteExtensionStore implements ExtensionStore if (!$selector->isInvisible()) { foreach ($selector->getComponents() as $component) { - $extender->originals->attach($component); + $extender->originals->offsetSet($component); } } @@ -224,7 +227,7 @@ class ConcreteExtensionStore implements ExtensionStore if (!$originalSelector->isInvisible()) { foreach ($originalSelector->getComponents() as $component) { - $this->originals->attach($component); + $this->originals->offsetSet($component); } } @@ -239,7 +242,7 @@ class ConcreteExtensionStore implements ExtensionStore $modifiableSelector = new ModifiableBox($selector); if ($mediaContext !== null) { - $this->mediaContexts->attach($modifiableSelector, $mediaContext); + $this->mediaContexts->offsetSet($modifiableSelector, $mediaContext); } $this->registerSelector($selector, $modifiableSelector); @@ -261,7 +264,7 @@ class ConcreteExtensionStore implements ExtensionStore if (!isset($this->selectors[$simple])) { /** @var ObjectSet> $set */ $set = new ObjectSet(); - $this->selectors->attach($simple, $set); + $this->selectors->offsetSet($simple, $set); } $this->selectors[$simple]->add($selector); @@ -422,7 +425,7 @@ class ConcreteExtensionStore implements ExtensionStore } } - if ($newExtensions->contains($extension->target)) { + if ($newExtensions->offsetExists($extension->target)) { /** @var SimpleSelectorMap> $additionalExtensions */ $additionalExtensions ??= new SimpleSelectorMap(); @@ -457,7 +460,7 @@ class ConcreteExtensionStore implements ExtensionStore try { $selector->setValue($this->extendList($selector->getValue(), $newExtensions, $this->mediaContexts[$selector] ?? null)); } catch (SassException $e) { - throw new SimpleSassException("From {$e->getSpan()->message('')}\n" . $e->getOriginalMessage(), $e->getSpan(), $e); + throw new SimpleSassException("From {$oldValue->getSpan()->message('')}\n" . $e->getOriginalMessage(), $e->getSpan(), $e); } // If no extends actually happened (for example because unification @@ -585,7 +588,7 @@ class ConcreteExtensionStore implements ExtensionStore return $list; } - return new SelectorList($this->trim($extended, $this->originals->contains(...)), $list->getSpan()); + return new SelectorList($this->trim($extended, $this->originals->offsetExists(...)), $list->getSpan()); } /** @@ -618,7 +621,7 @@ class ConcreteExtensionStore implements ExtensionStore // ] // $extendedNotExpanded = null; - $isOriginal = $this->originals->contains($complex); + $isOriginal = $this->originals->offsetExists($complex); foreach ($complex->getComponents() as $i => $component) { $extended = $this->extendCompound($component, $extensions, $mediaQueryContext, $isOriginal); @@ -684,8 +687,8 @@ class ConcreteExtensionStore implements ExtensionStore // Make sure that copies of $complex retain their status as "original" // selectors. This includes selectors that are modified because a :not() // was extended into. - if ($first && $this->originals->contains($complex)) { - $this->originals->attach($outputComplex); + if ($first && $this->originals->offsetExists($complex)) { + $this->originals->offsetSet($outputComplex); } $first = false; @@ -917,7 +920,7 @@ class ConcreteExtensionStore implements ExtensionStore if ($extensionsForSimple === null) { return null; } - $targetsUsed?->attach($simple); + $targetsUsed?->offsetSet($simple); $result = []; @@ -1122,6 +1125,11 @@ class ConcreteExtensionStore implements ExtensionStore */ private function trim(array $selectors, callable $isOriginal): array { + // Avoid truly horrific quadratic behavior. + if (\count($selectors) > 100) { + return $selectors; + } + // This is n² on the sequences, but only comparing between separate // sequences should limit the quadratic behavior. We iterate from last to // first and reverse the result so that, if two selectors are identical, we diff --git a/vendor/scssphp/scssphp/src/Extend/ObjectSet.php b/vendor/scssphp/scssphp/src/Extend/ObjectSet.php index 2a122b607..f22eefcf0 100644 --- a/vendor/scssphp/scssphp/src/Extend/ObjectSet.php +++ b/vendor/scssphp/scssphp/src/Extend/ObjectSet.php @@ -15,8 +15,10 @@ namespace ScssPhp\ScssPhp\Extend; /** * @template T of object * @template-implements \IteratorAggregate + * + * @internal */ -class ObjectSet implements \IteratorAggregate +final class ObjectSet implements \IteratorAggregate { /** * @var \SplObjectStorage @@ -33,7 +35,7 @@ class ObjectSet implements \IteratorAggregate */ public function contains(object $value): bool { - return $this->storage->contains($value); + return $this->storage->offsetExists($value); } /** @@ -41,7 +43,7 @@ class ObjectSet implements \IteratorAggregate */ public function add(object $value): void { - $this->storage->attach($value); + $this->storage->offsetSet($value); } /** diff --git a/vendor/scssphp/scssphp/src/Function/ColorFunctions.php b/vendor/scssphp/scssphp/src/Function/ColorFunctions.php index d879360e5..08d23030d 100644 --- a/vendor/scssphp/scssphp/src/Function/ColorFunctions.php +++ b/vendor/scssphp/scssphp/src/Function/ColorFunctions.php @@ -754,7 +754,7 @@ TXT, if (\count($list) > 3) { throw new SassScriptException(sprintf( - 'Only 3 elements allowed, but %s were passed', + 'Only 3 elements allowed, but %s were passed.', \count($list) )); } diff --git a/vendor/scssphp/scssphp/src/Function/FunctionRegistry.php b/vendor/scssphp/scssphp/src/Function/FunctionRegistry.php index 3ee125a5a..3ab4ce862 100644 --- a/vendor/scssphp/scssphp/src/Function/FunctionRegistry.php +++ b/vendor/scssphp/scssphp/src/Function/FunctionRegistry.php @@ -22,7 +22,7 @@ use ScssPhp\ScssPhp\Value\Value; class FunctionRegistry { /** - * @var array): Value>, url: string}> + * @var array): Value>, url?: string, canonical_name?: string}> */ private const BUILTIN_FUNCTIONS = [ // sass:color @@ -35,13 +35,13 @@ class FunctionRegistry '$red, $green, $blue' => [ColorFunctions::class, 'rgb'], '$color, $alpha' => [ColorFunctions::class, 'rgbTwoArgs'], '$channels' => [ColorFunctions::class, 'rgbOneArgs'], - ], 'url' => 'sass:color'], + ]], 'rgba' => ['overloads' => [ '$red, $green, $blue, $alpha' => [ColorFunctions::class, 'rgba'], '$red, $green, $blue' => [ColorFunctions::class, 'rgba'], '$color, $alpha' => [ColorFunctions::class, 'rgbaTwoArgs'], '$channels' => [ColorFunctions::class, 'rgbaOneArgs'], - ], 'url' => 'sass:color'], + ]], 'invert' => ['overloads' => ['$color, $weight: 100%' => [ColorFunctions::class, 'invert']], 'url' => 'sass:color'], 'hue' => ['overloads' => ['$color' => [ColorFunctions::class, 'hue']], 'url' => 'sass:color'], 'saturation' => ['overloads' => ['$color' => [ColorFunctions::class, 'saturation']], 'url' => 'sass:color'], @@ -52,13 +52,13 @@ class FunctionRegistry '$hue, $saturation, $lightness' => [ColorFunctions::class, 'hsl'], '$hue, $saturation' => [ColorFunctions::class, 'hslTwoArgs'], '$channels' => [ColorFunctions::class, 'hslOneArgs'], - ], 'url' => 'sass:color'], + ]], 'hsla' => ['overloads' => [ '$hue, $saturation, $lightness, $alpha' => [ColorFunctions::class, 'hsla'], '$hue, $saturation, $lightness' => [ColorFunctions::class, 'hsla'], '$hue, $saturation' => [ColorFunctions::class, 'hslaTwoArgs'], '$channels' => [ColorFunctions::class, 'hslaOneArgs'], - ], 'url' => 'sass:color'], + ]], 'grayscale' => ['overloads' => ['$color' => [ColorFunctions::class, 'grayscale']], 'url' => 'sass:color'], 'adjust-hue' => ['overloads' => ['$color, $degrees' => [ColorFunctions::class, 'adjustHue']], 'url' => 'sass:color'], 'lighten' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'lighten']], 'url' => 'sass:color'], @@ -66,7 +66,7 @@ class FunctionRegistry 'saturate' => ['overloads' => [ '$amount' => [ColorFunctions::class, 'saturateCss'], '$color, $amount' => [ColorFunctions::class, 'saturate'], - ], 'url' => 'sass:color'], + ]], 'desaturate' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'desaturate']], 'url' => 'sass:color'], 'opacify' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'opacify']], 'url' => 'sass:color'], 'fade-in' => ['overloads' => ['$color, $amount' => [ColorFunctions::class, 'opacify']], 'url' => 'sass:color'], @@ -75,12 +75,12 @@ class FunctionRegistry 'alpha' => ['overloads' => [ '$color' => [ColorFunctions::class, 'alpha'], '$args...' => [ColorFunctions::class, 'alphaMicrosoft'], - ], 'url' => 'sass:color'], + ]], 'opacity' => ['overloads' => ['$color' => [ColorFunctions::class, 'opacity']], 'url' => 'sass:color'], 'ie-hex-str' => ['overloads' => ['$color' => [ColorFunctions::class, 'ieHexStr']], 'url' => 'sass:color'], - 'adjust-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'adjust']], 'url' => 'sass:color'], - 'scale-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'scale']], 'url' => 'sass:color'], - 'change-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'change']], 'url' => 'sass:color'], + 'adjust-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'adjust']], 'url' => 'sass:color', 'canonical_name' => 'adjust'], + 'scale-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'scale']], 'url' => 'sass:color', 'canonical_name' => 'scale'], + 'change-color' => ['overloads' => ['$color, $kwargs...' => [ColorFunctions::class, 'change']], 'url' => 'sass:color', 'canonical_name' => 'change'], // sass:list 'length' => ['overloads' => ['$list' => [ListFunctions::class, 'length']], 'url' => 'sass:list'], 'nth' => ['overloads' => ['$list, $n' => [ListFunctions::class, 'nth']], 'url' => 'sass:list'], @@ -90,13 +90,13 @@ class FunctionRegistry 'zip' => ['overloads' => ['$lists...' => [ListFunctions::class, 'zip']], 'url' => 'sass:list'], 'index' => ['overloads' => ['$list, $value' => [ListFunctions::class, 'index']], 'url' => 'sass:list'], 'is-bracketed' => ['overloads' => ['$list' => [ListFunctions::class, 'isBracketed']], 'url' => 'sass:list'], - 'list-separator' => ['overloads' => ['$list' => [ListFunctions::class, 'separator']], 'url' => 'sass:list'], + 'list-separator' => ['overloads' => ['$list' => [ListFunctions::class, 'separator']], 'url' => 'sass:list', 'canonical_name' => 'separator'], // sass:map - 'map-get' => ['overloads' => ['$map, $key, $keys...' => [MapFunctions::class, 'get']], 'url' => 'sass:map'], + 'map-get' => ['overloads' => ['$map, $key, $keys...' => [MapFunctions::class, 'get']], 'url' => 'sass:map', 'canonical_name' => 'get'], 'map-merge' => ['overloads' => [ '$map1, $map2' => [MapFunctions::class, 'mergeTwoArgs'], '$map1, $args...' => [MapFunctions::class, 'mergeVariadic'], - ], 'url' => 'sass:map'], + ], 'canonical_name' => 'merge'], 'map-remove' => ['overloads' => [ // Because the signature below has an explicit `$key` argument, it doesn't // allow zero keys to be passed. We want to allow that case, so we add an @@ -105,10 +105,10 @@ class FunctionRegistry // The first argument has special handling so that the $key parameter can be // passed by name. '$map, $key, $keys...' => [MapFunctions::class, 'remove'], - ], 'url' => 'sass:map'], - 'map-keys' => ['overloads' => ['$map' => [MapFunctions::class, 'keys']], 'url' => 'sass:map'], - 'map-values' => ['overloads' => ['$map' => [MapFunctions::class, 'values']], 'url' => 'sass:map'], - 'map-has-key' => ['overloads' => ['$map, $key, $keys...' => [MapFunctions::class, 'hasKey']], 'url' => 'sass:map'], + ], 'canonical_name' => 'remove'], + 'map-keys' => ['overloads' => ['$map' => [MapFunctions::class, 'keys']], 'url' => 'sass:map', 'canonical_name' => 'keys'], + 'map-values' => ['overloads' => ['$map' => [MapFunctions::class, 'values']], 'url' => 'sass:map', 'canonical_name' => 'values'], + 'map-has-key' => ['overloads' => ['$map, $key, $keys...' => [MapFunctions::class, 'hasKey']], 'url' => 'sass:map', 'canonical_name' => 'has-key'], // sass:math 'abs' => ['overloads' => ['$number' => [MathFunctions::class, 'abs']], 'url' => 'sass:math'], 'ceil' => ['overloads' => ['$number' => [MathFunctions::class, 'ceil']], 'url' => 'sass:math'], @@ -119,8 +119,8 @@ class FunctionRegistry 'percentage' => ['overloads' => ['$number' => [MathFunctions::class, 'percentage']], 'url' => 'sass:math'], 'round' => ['overloads' => ['$number' => [MathFunctions::class, 'round']], 'url' => 'sass:math'], 'unit' => ['overloads' => ['$number' => [MathFunctions::class, 'unit']], 'url' => 'sass:math'], - 'comparable' => ['overloads' => ['$number1, $number2' => [MathFunctions::class, 'compatible']], 'url' => 'sass:math'], - 'unitless' => ['overloads' => ['$number' => [MathFunctions::class, 'isUnitless']], 'url' => 'sass:math'], + 'comparable' => ['overloads' => ['$number1, $number2' => [MathFunctions::class, 'compatible']], 'url' => 'sass:math', 'canonical_name' => 'compatible'], + 'unitless' => ['overloads' => ['$number' => [MathFunctions::class, 'isUnitless']], 'url' => 'sass:math', 'canonical_name' => 'is-unitless'], // sass:meta 'feature-exists' => ['overloads' => ['$feature' => [MetaFunctions::class, 'featureExists']], 'url' => 'sass:meta'], 'inspect' => ['overloads' => ['$value' => [MetaFunctions::class, 'inspect']], 'url' => 'sass:meta'], @@ -129,22 +129,25 @@ class FunctionRegistry // sass:selector 'is-superselector' => ['overloads' => ['$super, $sub' => [SelectorFunctions::class, 'isSuperselector']], 'url' => 'sass:selector'], 'simple-selectors' => ['overloads' => ['$selector' => [SelectorFunctions::class, 'simpleSelectors']], 'url' => 'sass:selector'], - 'selector-parse' => ['overloads' => ['$selector' => [SelectorFunctions::class, 'parse']], 'url' => 'sass:selector'], - 'selector-nest' => ['overloads' => ['$selectors...' => [SelectorFunctions::class, 'nest']], 'url' => 'sass:selector'], - 'selector-append' => ['overloads' => ['$selectors...' => [SelectorFunctions::class, 'append']], 'url' => 'sass:selector'], - 'selector-extend' => ['overloads' => ['$selector, $extendee, $extender' => [SelectorFunctions::class, 'extend']], 'url' => 'sass:selector'], - 'selector-replace' => ['overloads' => ['$selector, $original, $replacement' => [SelectorFunctions::class, 'replace']], 'url' => 'sass:selector'], - 'selector-unify' => ['overloads' => ['$selector1, $selector2' => [SelectorFunctions::class, 'unify']], 'url' => 'sass:selector'], + 'selector-parse' => ['overloads' => ['$selector' => [SelectorFunctions::class, 'parse']], 'url' => 'sass:selector', 'canonical_name' => 'parse'], + 'selector-nest' => ['overloads' => ['$selectors...' => [SelectorFunctions::class, 'nest']], 'url' => 'sass:selector', 'canonical_name' => 'nest'], + 'selector-append' => ['overloads' => ['$selectors...' => [SelectorFunctions::class, 'append']], 'url' => 'sass:selector', 'canonical_name' => 'append'], + 'selector-extend' => ['overloads' => ['$selector, $extendee, $extender' => [SelectorFunctions::class, 'extend']], 'url' => 'sass:selector', 'canonical_name' => 'extend'], + 'selector-replace' => ['overloads' => ['$selector, $original, $replacement' => [SelectorFunctions::class, 'replace']], 'url' => 'sass:selector', 'canonical_name' => 'replace'], + 'selector-unify' => ['overloads' => ['$selector1, $selector2' => [SelectorFunctions::class, 'unify']], 'url' => 'sass:selector', 'canonical_name' => 'unify'], // sass:string 'unquote' => ['overloads' => ['$string' => [StringFunctions::class, 'unquote']], 'url' => 'sass:string'], 'quote' => ['overloads' => ['$string' => [StringFunctions::class, 'quote']], 'url' => 'sass:string'], 'to-upper-case' => ['overloads' => ['$string' => [StringFunctions::class, 'toUpperCase']], 'url' => 'sass:string'], 'to-lower-case' => ['overloads' => ['$string' => [StringFunctions::class, 'toLowerCase']], 'url' => 'sass:string'], 'unique-id' => ['overloads' => ['' => [StringFunctions::class, 'uniqueId']], 'url' => 'sass:string'], - 'str-length' => ['overloads' => ['$string' => [StringFunctions::class, 'length']], 'url' => 'sass:string'], - 'str-insert' => ['overloads' => ['$string, $insert, $index' => [StringFunctions::class, 'insert']], 'url' => 'sass:string'], - 'str-index' => ['overloads' => ['$string, $substring' => [StringFunctions::class, 'index']], 'url' => 'sass:string'], - 'str-slice' => ['overloads' => ['$string, $start-at, $end-at: -1' => [StringFunctions::class, 'slice']], 'url' => 'sass:string'], + 'str-length' => ['overloads' => ['$string' => [StringFunctions::class, 'length']], 'url' => 'sass:string', 'canonical_name' => 'length'], + 'str-insert' => ['overloads' => ['$string, $insert, $index' => [StringFunctions::class, 'insert']], 'url' => 'sass:string', 'canonical_name' => 'insert'], + 'str-index' => ['overloads' => ['$string, $substring' => [StringFunctions::class, 'index']], 'url' => 'sass:string', 'canonical_name' => 'index'], + 'str-slice' => ['overloads' => ['$string, $start-at, $end-at: -1' => [StringFunctions::class, 'slice']], 'url' => 'sass:string', 'canonical_name' => 'slice'], + // special + // This is only invoked using `call()`. Hand-authored `if()`s are parsed as IfExpression. + 'if' => ['overloads' => ['$condition, $if-true, $if-false' => [self::class, 'if']]], ]; /** @@ -172,11 +175,27 @@ class FunctionRegistry throw new \InvalidArgumentException("There is no builtin function named $name."); } - return BuiltInCallable::overloadedFunction($name, self::BUILTIN_FUNCTIONS[$name]['overloads'], Uri::new(self::BUILTIN_FUNCTIONS[$name]['url'])); + $url = isset(self::BUILTIN_FUNCTIONS[$name]['url']) ? Uri::new(self::BUILTIN_FUNCTIONS[$name]['url']) : null; + + $callable = BuiltInCallable::overloadedFunction(self::BUILTIN_FUNCTIONS[$name]['canonical_name'] ?? $name, self::BUILTIN_FUNCTIONS[$name]['overloads'], $url); + + if (isset(self::BUILTIN_FUNCTIONS[$name]['canonical_name'])) { + $callable = $callable->withName($name); + } + + return $callable; } public static function isBuiltinFunction(string $name): bool { return isset(self::BUILTIN_FUNCTIONS[$name]) || \in_array($name, self::SPECIAL_META_GLOBAL_FUNCTIONS, true); } + + /** + * @param list $arguments + */ + public static function if(array $arguments): Value + { + return $arguments[0]->isTruthy() ? $arguments[1] : $arguments[2]; + } } diff --git a/vendor/scssphp/scssphp/src/Function/StringFunctions.php b/vendor/scssphp/scssphp/src/Function/StringFunctions.php index 20fbf187e..3478a824d 100644 --- a/vendor/scssphp/scssphp/src/Function/StringFunctions.php +++ b/vendor/scssphp/scssphp/src/Function/StringFunctions.php @@ -125,12 +125,12 @@ final class StringFunctions // No matter what the start index is, an end index of 0 will produce an // empty string. - $endInt = $end->assertInt('end-at'); + $endInt = $end->assertInt(); if ($endInt === 0) { return new SassString('', $string->hasQuotes()); } - $startCodepoint = self::codepointForIndex($start->assertInt('start-at'), $lengthInCodepoints); + $startCodepoint = self::codepointForIndex($start->assertInt(), $lengthInCodepoints); $endCodepoint = self::codepointForIndex($endInt, $lengthInCodepoints, true); if ($endCodepoint === $lengthInCodepoints) { diff --git a/vendor/scssphp/scssphp/src/Logger/StreamLogger.php b/vendor/scssphp/scssphp/src/Logger/StreamLogger.php index f11f0f19e..1e920bd9b 100644 --- a/vendor/scssphp/scssphp/src/Logger/StreamLogger.php +++ b/vendor/scssphp/scssphp/src/Logger/StreamLogger.php @@ -24,8 +24,11 @@ use SourceSpan\SourceSpan; */ final class StreamLogger implements LoggerInterface { + /** + * @var resource + */ private $stream; - private $closeOnDestruct; + private bool $closeOnDestruct; /** * @param resource $stream A stream resource diff --git a/vendor/scssphp/scssphp/src/Parser/InterpolationMap.php b/vendor/scssphp/scssphp/src/Parser/InterpolationMap.php index b38d8b1d2..2d876a51e 100644 --- a/vendor/scssphp/scssphp/src/Parser/InterpolationMap.php +++ b/vendor/scssphp/scssphp/src/Parser/InterpolationMap.php @@ -59,15 +59,20 @@ final class InterpolationMap public function mapException(FormatException $error): FormatException { - $source = $this->mapSpan($error->getSpan()); - $startIndex = $this->indexInContents($source->getStart()); - $endIndex = $this->indexInContents($source->getEnd()); + if (\count($this->interpolation->getContents()) === 0) { + return new FormatException($error->getMessage(), $this->interpolation->getSpan(), $error); + } + + $target = $error->getSpan(); + $source = $this->mapSpan($target); + $startIndex = $this->indexInContents($target->getStart()); + $endIndex = $this->indexInContents($target->getEnd()); if (!IterableUtil::any(array_slice($this->interpolation->getContents(), $startIndex, $endIndex - $startIndex + 1), fn ($content) => $content instanceof Expression)) { return new FormatException($error->getMessage(), $source, $error); } - return new MultiSourceFormatException($error->getMessage(), $source, '', ['error in interpolated output' => $error->getSpan()], $error); + return new MultiSourceFormatException($error->getMessage(), $source, '', ['error in interpolated output' => $target], $error); } public function mapSpan(FileSpan $target): FileSpan @@ -95,6 +100,10 @@ final class InterpolationMap */ private function mapLocation(SourceLocation $target): object { + if (\count($this->interpolation->getContents()) === 0) { + return $this->interpolation->getSpan(); + } + $index = $this->indexInContents($target); $components = $this->interpolation->getContents(); @@ -116,6 +125,9 @@ final class InterpolationMap return $previousLocation->getFile()->location($previousLocation->getOffset() + $offsetInString); } + /** + * @return int<0, max> + */ private function indexInContents(SourceLocation $target): int { foreach ($this->targetLocations as $i => $location) { @@ -124,6 +136,8 @@ final class InterpolationMap } } + \assert(\count($this->interpolation->getContents()) > 0); + return \count($this->interpolation->getContents()) - 1; } diff --git a/vendor/scssphp/scssphp/src/Parser/MultiSourceFormatException.php b/vendor/scssphp/scssphp/src/Parser/MultiSourceFormatException.php index e226029af..b660f1f00 100644 --- a/vendor/scssphp/scssphp/src/Parser/MultiSourceFormatException.php +++ b/vendor/scssphp/scssphp/src/Parser/MultiSourceFormatException.php @@ -14,7 +14,10 @@ namespace ScssPhp\ScssPhp\Parser; use SourceSpan\FileSpan; -class MultiSourceFormatException extends FormatException +/** + * @internal + */ +final class MultiSourceFormatException extends FormatException { /** * {@see MultiSpanSassException::$primaryLabel} diff --git a/vendor/scssphp/scssphp/src/Parser/Parser.php b/vendor/scssphp/scssphp/src/Parser/Parser.php index 61c52100a..455be3a50 100644 --- a/vendor/scssphp/scssphp/src/Parser/Parser.php +++ b/vendor/scssphp/scssphp/src/Parser/Parser.php @@ -586,12 +586,12 @@ class Parser } if ($valueText === false) { - $this->scanner->error('Invalid Unicode code point.', $start); + $this->scanner->error('Invalid Unicode code point.', $start, $this->scanner->getPosition() - $start); } if ($identifierStart ? Character::isNameStart($valueText) : Character::isName($valueText)) { if ($value > 0x10ffff) { - $this->scanner->error('Invalid Unicode code point.', $start); + $this->scanner->error('Invalid Unicode code point.', $start, $this->scanner->getPosition() - $start); } return $valueText; @@ -686,7 +686,7 @@ class Parser return; } - $this->scanner->error("Expected \"$char\""); + $this->scanner->error("Expected \"$char\"."); } /** diff --git a/vendor/scssphp/scssphp/src/Parser/StringScanner.php b/vendor/scssphp/scssphp/src/Parser/StringScanner.php index 9fabb48ff..c0e665bcc 100644 --- a/vendor/scssphp/scssphp/src/Parser/StringScanner.php +++ b/vendor/scssphp/scssphp/src/Parser/StringScanner.php @@ -44,6 +44,10 @@ class StringScanner private readonly SourceFile $sourceFile; + private ?int $lastMatchStart = null; + + private ?int $lastMatchPosition = null; + public function __construct(string $content, ?UriInterface $sourceUrl = null) { $this->string = $content; @@ -63,6 +67,7 @@ class StringScanner public function setPosition(int $position): void { $this->position = $position; + $this->lastMatchStart = null; } public function spanFrom(int $start, ?int $end = null): FileSpan @@ -165,6 +170,7 @@ class StringScanner } $this->position += \strlen($string); + $this->lastMatchPosition = $this->position; return true; } @@ -180,7 +186,14 @@ class StringScanner return false; } - return substr($this->string, $this->position, \strlen($string)) === $string; + if (substr($this->string, $this->position, \strlen($string)) === $string) { + $this->lastMatchStart = $this->position; + $this->lastMatchPosition = $this->position; + + return true; + } + + return false; } /** @@ -291,6 +304,12 @@ class StringScanner */ public function error(string $message, ?int $position = null, ?int $length = null): never { + if ($position === null && $length === null && $this->getLastMatchStart() !== null) { + \assert($this->lastMatchStart !== null); + $position = $this->lastMatchStart; + $length = $this->position - $position; + } + $position ??= $this->position; $length ??= 0; @@ -299,6 +318,17 @@ class StringScanner throw new FormatException($message, $span); } + private function getLastMatchStart(): ?int + { + // Lazily unset $this->lastMatchStart so that we avoid extra assignments in + // character-by-character methods that are used in core loops. + if ($this->lastMatchPosition !== $this->position) { + $this->lastMatchStart = null; + } + + return $this->lastMatchStart; + } + /** * @throws FormatException */ diff --git a/vendor/scssphp/scssphp/src/SassCallable/BuiltInCallable.php b/vendor/scssphp/scssphp/src/SassCallable/BuiltInCallable.php index 4c2e79f7f..0037b93bf 100644 --- a/vendor/scssphp/scssphp/src/SassCallable/BuiltInCallable.php +++ b/vendor/scssphp/scssphp/src/SassCallable/BuiltInCallable.php @@ -112,7 +112,7 @@ class BuiltInCallable implements SassCallable foreach ($overloads as $args => $callback) { $processedOverloads[] = [ ArgumentDeclaration::parse("@function $name($args) {", url: $url), - $callback + $callback, ]; } @@ -175,7 +175,7 @@ class BuiltInCallable implements SassCallable $mismatchDistance = \count($overload[0]->getArguments()) - $positional; if ($minMismatchDistance !== null) { - if (abs($mismatchDistance) > $minMismatchDistance) { + if (abs($mismatchDistance) > abs($minMismatchDistance)) { continue; } @@ -196,4 +196,12 @@ class BuiltInCallable implements SassCallable throw new \LogicException("BuiltInCallable {$this->name} may not have empty overloads."); } + + /** + * Returns a copy of this callable with the given $name. + */ + public function withName(string $name): BuiltInCallable + { + return new BuiltInCallable($name, $this->overloads, $this->acceptsContent); + } } diff --git a/vendor/scssphp/scssphp/src/Serializer/SerializeVisitor.php b/vendor/scssphp/scssphp/src/Serializer/SerializeVisitor.php index 4d46e9112..f41f68941 100644 --- a/vendor/scssphp/scssphp/src/Serializer/SerializeVisitor.php +++ b/vendor/scssphp/scssphp/src/Serializer/SerializeVisitor.php @@ -600,7 +600,7 @@ final class SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisito private function writeCalculationValue(object $value): void { if ($value instanceof SassNumber && $value->hasComplexUnits() && !$this->inspect) { - throw new SassScriptException("$value is not a valid CSS value."); + throw new SassScriptException("$value isn't a valid CSS value."); } if ($value instanceof SassNumber && !is_finite($value->getValue())) { if (is_nan($value->getValue())) { @@ -837,7 +837,7 @@ final class SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisito public function visitFunction(SassFunction $value): void { if (!$this->inspect) { - throw new SassScriptException("$value is not a valid CSS value."); + throw new SassScriptException("$value isn't a valid CSS value."); } $this->buffer->write('get-function('); @@ -848,7 +848,7 @@ final class SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisito public function visitMixin(SassMixin $value): void { if (!$this->inspect) { - throw new SassScriptException("$value is not a valid CSS value."); + throw new SassScriptException("$value isn't a valid CSS value."); } $this->buffer->write('get-mixin('); @@ -862,7 +862,7 @@ final class SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisito $this->buffer->writeChar('['); } elseif (\count($value->asList()) === 0) { if (!$this->inspect) { - throw new SassScriptException("() is not a valid CSS value."); + throw new SassScriptException("() isn't a valid CSS value."); } $this->buffer->write('()'); @@ -959,7 +959,7 @@ final class SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisito public function visitMap(SassMap $value): void { if (!$this->inspect) { - throw new SassScriptException("$value is not a valid CSS value."); + throw new SassScriptException("$value isn't a valid CSS value."); } $this->buffer->writeChar('('); @@ -1023,7 +1023,7 @@ final class SerializeVisitor implements CssVisitor, ValueVisitor, SelectorVisito if ($value->hasComplexUnits()) { if (!$this->inspect) { - throw new SassScriptException("$value is not a valid CSS value."); + throw new SassScriptException("$value isn't a valid CSS value."); } $this->visitCalculation(SassCalculation::unsimplified('calc', [$value])); diff --git a/vendor/scssphp/scssphp/src/Serializer/Serializer.php b/vendor/scssphp/scssphp/src/Serializer/Serializer.php index 385020f88..603851973 100644 --- a/vendor/scssphp/scssphp/src/Serializer/Serializer.php +++ b/vendor/scssphp/scssphp/src/Serializer/Serializer.php @@ -13,11 +13,14 @@ namespace ScssPhp\ScssPhp\Serializer; use ScssPhp\ScssPhp\Ast\Css\CssNode; +use ScssPhp\ScssPhp\Ast\Css\CssParentNode; use ScssPhp\ScssPhp\Ast\Selector\Selector; use ScssPhp\ScssPhp\Exception\SassScriptException; use ScssPhp\ScssPhp\Logger\LoggerInterface; use ScssPhp\ScssPhp\OutputStyle; use ScssPhp\ScssPhp\Value\Value; +use ScssPhp\ScssPhp\Visitor\CssVisitor; +use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor; /** * @internal @@ -58,6 +61,10 @@ final class Serializer */ public static function serializeValue(Value $value, bool $inspect = false, bool $quote = true): string { + // Force loading the CssParentNode and CssVisitor before using the visitor because of a weird PHP behavior. + class_exists(CssParentNode::class); + class_exists(CssVisitor::class); + $visitor = new SerializeVisitor($inspect, $quote); $value->accept($visitor); @@ -74,6 +81,10 @@ final class Serializer */ public static function serializeSelector(Selector $selector, bool $inspect = false): string { + // Force loading the CssParentNode and CssVisitor before using the visitor because of a weird PHP behavior. + class_exists(CssParentNode::class); + class_exists(CssVisitor::class); + $visitor = new SerializeVisitor($inspect); $selector->accept($visitor); diff --git a/vendor/scssphp/scssphp/src/Util/Box.php b/vendor/scssphp/scssphp/src/Util/Box.php index f3435851f..a9b476d8b 100644 --- a/vendor/scssphp/scssphp/src/Util/Box.php +++ b/vendor/scssphp/scssphp/src/Util/Box.php @@ -19,6 +19,8 @@ namespace ScssPhp\ScssPhp\Util; * when the underlying type uses value equality. * * @template T + * + * @internal */ final class Box implements Equatable { diff --git a/vendor/scssphp/scssphp/src/Util/Character.php b/vendor/scssphp/scssphp/src/Util/Character.php index f5535f24f..8f014168a 100644 --- a/vendor/scssphp/scssphp/src/Util/Character.php +++ b/vendor/scssphp/scssphp/src/Util/Character.php @@ -62,7 +62,7 @@ final class Character */ public static function isAlphabetic(string $character): bool { - $charCode = \ord($character); + $charCode = \ord($character[0]); return ($charCode >= \ord('a') && $charCode <= \ord('z')) || ($charCode >= \ord('A') && $charCode <= \ord('Z')); } @@ -76,7 +76,7 @@ final class Character return false; } - $charCode = \ord($character); + $charCode = \ord($character[0]); return $charCode >= \ord('0') && $charCode <= \ord('9'); } @@ -86,7 +86,7 @@ final class Character */ public static function isNameStart(string $character): bool { - return $character === '_' || self::isAlphabetic($character) || \ord($character) >= 0x80; + return $character === '_' || self::isAlphabetic($character) || \ord($character[0]) >= 0x80; } /** @@ -110,7 +110,7 @@ final class Character return true; } - $charCode = \ord($character); + $charCode = \ord($character[0]); if ($charCode >= \ord('a') && $charCode <= \ord('f')) { return true; @@ -157,12 +157,12 @@ final class Character // If this check fails, the characters are definitely different. If it // succeeds *and* either character is an ASCII letter, they're equivalent. - if ((\ord($character1) ^ \ord($character2)) !== self::ASCII_CASE_BIT) { + if ((\ord($character1[0]) ^ \ord($character2[0])) !== self::ASCII_CASE_BIT) { return false; } // Now we just need to verify that one of the characters is an ASCII letter. - $upperCase1 = \ord($character1) & ~self::ASCII_CASE_BIT; + $upperCase1 = \ord($character1[0]) & ~self::ASCII_CASE_BIT; return $upperCase1 >= \ord('A') && $upperCase1 <= \ord('Z'); } diff --git a/vendor/scssphp/scssphp/src/Util/ErrorUtil.php b/vendor/scssphp/scssphp/src/Util/ErrorUtil.php index 10d3e525e..3e244f96a 100644 --- a/vendor/scssphp/scssphp/src/Util/ErrorUtil.php +++ b/vendor/scssphp/scssphp/src/Util/ErrorUtil.php @@ -64,34 +64,4 @@ final class ErrorUtil return $formattedMessage; } - - /** - * Check that a range represents a slice of an indexable object. - * - * Throws if the range is not valid for an indexable object with - * the given length. - * A range is valid for an indexable object with a given $length - * if `0 <= $start <= $end <= $length`. - * An `end` of `null` is considered equivalent to `length`. - * - * @throws \OutOfRangeException - */ - public static function checkValidRange(int $start, ?int $end, int $length, ?string $startName = null, ?string $endName = null): void - { - if ($start < 0 || $start > $length) { - $startName ??= 'start'; - $startNameDisplay = $startName ? " $startName" : ''; - - throw new \OutOfRangeException("Invalid value:$startNameDisplay must be between 0 and $length: $start."); - } - - if ($end !== null) { - if ($end < $start || $end > $length) { - $endName ??= 'end'; - $endNameDisplay = $endName ? " $endName" : ''; - - throw new \OutOfRangeException("Invalid value:$endNameDisplay must be between $start and $length: $end."); - } - } - } } diff --git a/vendor/scssphp/scssphp/src/Util/ListUtil.php b/vendor/scssphp/scssphp/src/Util/ListUtil.php index df5ca49fa..c76c711bc 100644 --- a/vendor/scssphp/scssphp/src/Util/ListUtil.php +++ b/vendor/scssphp/scssphp/src/Util/ListUtil.php @@ -93,8 +93,8 @@ final class ListUtil } /** - * @param int $i - * @param int $j + * @param int<-1, max> $i + * @param int<-1, max> $j * @return list */ $backtrack = function (int $i, int $j) use ($selections, $lengths, &$backtrack) { @@ -102,6 +102,9 @@ final class ListUtil return []; } + \assert($i >= 0); + \assert($j >= 0); + $selection = $selections[$i][$j]; if ($selection !== null) { diff --git a/vendor/scssphp/scssphp/src/Util/Path.php b/vendor/scssphp/scssphp/src/Util/Path.php index eb183927c..2c0eabb7c 100644 --- a/vendor/scssphp/scssphp/src/Util/Path.php +++ b/vendor/scssphp/scssphp/src/Util/Path.php @@ -2,7 +2,6 @@ namespace ScssPhp\ScssPhp\Util; -use League\Uri\BaseUri; use League\Uri\Contracts\UriInterface; use League\Uri\Uri; use Symfony\Component\Filesystem\Exception\InvalidArgumentException; @@ -28,11 +27,15 @@ final class Path public static function fromUri(UriInterface $uri): string { - if (\DIRECTORY_SEPARATOR === '\\') { - return BaseUri::from($uri)->windowsPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + if (!$uri instanceof Uri) { + $uri = Uri::new($uri); } - return BaseUri::from($uri)->unixPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + if (\DIRECTORY_SEPARATOR === '\\') { + return $uri->toWindowsPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + } + + return $uri->toUnixPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); } public static function isAbsolute(string $path): bool diff --git a/vendor/scssphp/scssphp/src/Util/UriUtil.php b/vendor/scssphp/scssphp/src/Util/UriUtil.php index e0dbc34ad..bffd9f192 100644 --- a/vendor/scssphp/scssphp/src/Util/UriUtil.php +++ b/vendor/scssphp/scssphp/src/Util/UriUtil.php @@ -12,8 +12,9 @@ namespace ScssPhp\ScssPhp\Util; -use League\Uri\BaseUri; use League\Uri\Contracts\UriInterface; +use League\Uri\Uri; +use League\Uri\UriString; /** * @internal @@ -22,19 +23,135 @@ final class UriUtil { public static function resolve(UriInterface $baseUrl, string $reference): UriInterface { - $resolvedUri = BaseUri::from($baseUrl)->resolve($reference)->getUri(); - - \assert($resolvedUri instanceof UriInterface); - - return $resolvedUri; + return self::resolveUri($baseUrl, Uri::new($reference)); } public static function resolveUri(UriInterface $baseUrl, UriInterface $url): UriInterface { - $resolvedUri = BaseUri::from($baseUrl)->resolve($url)->getUri(); + if ($baseUrl->getScheme() !== null) { + // non-RFC3986 behavior in Dart-Sass when resolving relative reference with a base url with no authority and a relative path (where they consider the base path as absolute) + if ($baseUrl->getAuthority() === null && $baseUrl->getPath() !== '' && $baseUrl->getPath()[0] !== '/' && $url->getScheme() === null && $url->getAuthority() === null && $url->getPath() !== '' && $url->getPath()[0] !== '/') { + return self::resolveLeagueUri($baseUrl->withPath('/' . $baseUrl->getPath()), $url); + } - \assert($resolvedUri instanceof UriInterface); + return self::resolveLeagueUri($baseUrl, $url); + } - return $resolvedUri; + if ($url->getScheme() !== null) { + return $url->withPath(UriString::removeDotSegments($url->getPath())); + } + + if ($baseUrl->getAuthority() !== null || $url->getAuthority() !== null) { + return self::resolveLeagueUri($baseUrl->withScheme('scssphp-resolve'), $url)->withScheme(null); + } + + if ($url->getPath() === '') { + if ($url->getQuery() !== null) { + return $baseUrl->withQuery($url->getQuery())->withFragment($url->getFragment()); + } + + if ($url->getFragment() !== null) { + return $baseUrl->withFragment($url->getFragment()); + } + + return $baseUrl; + } + + if ($url->getPath()[0] === '/') { + return $url->withPath(UriString::removeDotSegments($url->getPath())); + } + + if ($baseUrl->getPath() === '') { + return $url; + } + + if ($baseUrl->getPath()[0] !== '/') { + // Pure path resolution between 2 relative path URLs + $mergedPath = self::normalizeRelativePath(self::mergePaths($baseUrl->getPath(), $url->getPath())); + + return $url->withPath($mergedPath); + } + + return self::resolveLeagueUri($baseUrl->withScheme('scssphp-resolve')->withHost('localhost'), $url)->withScheme(null)->withHost(null); + } + + private static function resolveLeagueUri(UriInterface $baseUrl, UriInterface $url): UriInterface + { + // Custom implementations of UriInterface might not implement the resolve method yet, until version 8.0 of the interface. + if (!$baseUrl instanceof Uri && !method_exists($baseUrl, 'resolve')) { + $baseUrl = Uri::new($baseUrl); + } + + return $baseUrl->resolve($url); + } + + /** + * @param non-empty-string $base + * @param non-empty-string $reference + * + * @return non-empty-string + */ + private static function mergePaths(string $base, string $reference): string + { + \assert($reference[0] !== '/'); + + $baseEnd = strrpos($base, '/'); + + if ($baseEnd === false) { + return $reference; + } + + return substr($base, 0, $baseEnd + 1) . $reference; + } + + /** + * Removes all `.` segments and any non-leading `..` segments. + * + * Removing the ".." from a "bar/foo/.." sequence results in "bar/" + * (trailing "/"). If the entire path is removed (because it contains as + * many ".." segments as real segments), the result is "./". + * This is different from an empty string, which represents "no path" + * when you resolve it against a base URI with a path with a non-empty + * final segment. + * + * @param non-empty-string $path + */ + private static function normalizeRelativePath(string $path): string + { + \assert($path[0] !== '/'); + + if ($path[0] !== '.' && !str_contains($path, '/.')) { + return $path; + } + + $output = []; + $appendSlash = false; + + foreach (explode('/', $path) as $segment) { + $appendSlash = false; + + if ('..' === $segment) { + if ($output !== [] && ListUtil::last($output) !== '..') { + array_pop($output); + $appendSlash = true; + } else { + $output[] = '..'; + } + } elseif ('.' === $segment) { + $appendSlash = true; + } else { + $output[] = $segment; + } + } + + if ($output === [] || $output === ['']) { + return './'; + } + + if ($appendSlash || ListUtil::last($output) === '..') { + $output[] = ''; + } + + return implode('/', $output); } } diff --git a/vendor/scssphp/scssphp/src/Value/SassNumber.php b/vendor/scssphp/scssphp/src/Value/SassNumber.php index 4255ede5a..b48099a05 100644 --- a/vendor/scssphp/scssphp/src/Value/SassNumber.php +++ b/vendor/scssphp/scssphp/src/Value/SassNumber.php @@ -644,7 +644,7 @@ abstract class SassNumber extends Value return SassBoolean::create($this->coerceUnits($other, NumberUtil::fuzzyLessThanOrEquals(...))); } - throw new SassScriptException("Undefined operation \"$this > $other\"."); + throw new SassScriptException("Undefined operation \"$this <= $other\"."); } public function modulo(Value $other): SassNumber @@ -759,6 +759,7 @@ abstract class SassNumber extends Value { foreach (self::CONVERSIONS as $canonicalUnit => $conversions) { if (isset($conversions[$unit])) { + \assert(isset($conversions[$canonicalUnit])); return $conversions[$canonicalUnit] / $conversions[$unit]; } } @@ -906,7 +907,7 @@ abstract class SassNumber extends Value $message .= " \$$otherName:"; } - $message .= "$other have incompatible units"; + $message .= " $other have incompatible units"; if (!$this->hasUnits() || !$otherHasUnits) { $message .= " (one has units and the other doesn't)"; diff --git a/vendor/scssphp/scssphp/src/ValueConverter.php b/vendor/scssphp/scssphp/src/ValueConverter.php index c874f27f2..5e7d04cb3 100644 --- a/vendor/scssphp/scssphp/src/ValueConverter.php +++ b/vendor/scssphp/scssphp/src/ValueConverter.php @@ -12,9 +12,13 @@ namespace ScssPhp\ScssPhp; +use ScssPhp\ScssPhp\Collection\Map; use ScssPhp\ScssPhp\Logger\QuietLogger; use ScssPhp\ScssPhp\Node\Number; +use ScssPhp\ScssPhp\Value\ListSeparator; use ScssPhp\ScssPhp\Value\SassBoolean; +use ScssPhp\ScssPhp\Value\SassList; +use ScssPhp\ScssPhp\Value\SassMap; use ScssPhp\ScssPhp\Value\SassNull; use ScssPhp\ScssPhp\Value\SassNumber; use ScssPhp\ScssPhp\Value\SassString; @@ -77,11 +81,6 @@ final class ValueConverter return SassNumber::withUnits($value->getDimension(), $value->getNumeratorUnits(), $value->getDenominatorUnits()); } - if (is_array($value) && isset($value[0]) && \in_array($value[0], [Type::T_NULL, Type::T_COLOR, Type::T_KEYWORD, Type::T_LIST, Type::T_MAP, Type::T_STRING])) { - // TODO convert legacy value - throw new \LogicException('Not implemented'); - } - if ($value === null) { return SassNull::create(); } @@ -106,6 +105,25 @@ final class ValueConverter return new SassString($value); } - throw new \InvalidArgumentException(sprintf('Cannot convert the value of type "%s" to a Sass value.', gettype($value))); + if (\is_array($value)) { + if (array_is_list($value)) { + $result = []; + foreach ($value as $val) { + $result[] = self::fromPhp($val); + } + + return new SassList($result, \count($result) > 0 ? ListSeparator::COMMA : ListSeparator::UNDECIDED); + } + + /** @var Map $map */ + $map = new Map(); + foreach ($value as $key => $val) { + $map->put(new SassString($key), self::fromPhp($val)); + } + + return SassMap::create($map); + } + + throw new \InvalidArgumentException(sprintf('Cannot convert the value of type "%s" to a Sass value.', get_debug_type($value))); } } diff --git a/vendor/scssphp/scssphp/src/Version.php b/vendor/scssphp/scssphp/src/Version.php index 411c7be35..c1e1f1718 100644 --- a/vendor/scssphp/scssphp/src/Version.php +++ b/vendor/scssphp/scssphp/src/Version.php @@ -19,5 +19,5 @@ namespace ScssPhp\ScssPhp; */ final class Version { - const VERSION = '2.0.1'; + const VERSION = '2.1.0'; } diff --git a/vendor/scssphp/source-span/composer.json b/vendor/scssphp/source-span/composer.json index d76e32f58..844e3c8b1 100644 --- a/vendor/scssphp/source-span/composer.json +++ b/vendor/scssphp/source-span/composer.json @@ -20,16 +20,17 @@ }, "require": { "php": ">=8.1", - "league/uri": "^7.4", - "league/uri-interfaces": "^7.4" + "ext-mbstring": "*", + "league/uri": "^7.6", + "league/uri-interfaces": "^7.6" }, "require-dev": { "phpstan/phpstan": "^2.0", "phpstan/phpstan-deprecation-rules": "^2.0", "phpunit/phpunit": "^9.5.6", "squizlabs/php_codesniffer": "~3.5", - "symfony/phpunit-bridge": "^5.1", - "symfony/var-dumper": "^6.3" + "symfony/phpunit-bridge": "^6.4 || ^7.3 || ^8.0", + "symfony/var-dumper": "^6.4 || ^7.3 || ^8.0" }, "extra": { "branch-alias": { diff --git a/vendor/scssphp/source-span/src/SimpleSourceLocation.php b/vendor/scssphp/source-span/src/SimpleSourceLocation.php index 591f6e4b0..2d7976c3c 100644 --- a/vendor/scssphp/source-span/src/SimpleSourceLocation.php +++ b/vendor/scssphp/source-span/src/SimpleSourceLocation.php @@ -12,7 +12,7 @@ final class SimpleSourceLocation extends SourceLocationMixin /** * Creates a new location indicating $offset within $sourceUrl. * - * $line and $column default to assuming the source is a single line. This + * $line and $column default to assuming the source is a single ASCII line. This * means that $line defaults to 0 and $column defaults to $offset. */ public function __construct( diff --git a/vendor/scssphp/source-span/src/SourceFile.php b/vendor/scssphp/source-span/src/SourceFile.php index 33bc5502c..52627fbdc 100644 --- a/vendor/scssphp/source-span/src/SourceFile.php +++ b/vendor/scssphp/source-span/src/SourceFile.php @@ -212,12 +212,15 @@ final class SourceFile /** * The 0-based column of that offset. + * + * Unlike offsets (which are byte-offsets), columns are computed based on Unicode + * codepoints to provide a better experience. */ public function getColumn(int $offset): int { $line = $this->getLine($offset); - return $offset - $this->lineStarts[$line]; + return mb_strlen(substr($this->string, $this->lineStarts[$line], $offset - $this->lineStarts[$line]), 'UTF-8'); } /** @@ -237,7 +240,17 @@ final class SourceFile throw new \OutOfRangeException('Column may not be negative.'); } - $result = $this->lineStarts[$line] + $column; + if ($column === 0) { + $result = $this->lineStarts[$line]; + } else { + $lineContent = substr($this->string, $this->lineStarts[$line], $this->lineStarts[$line + 1] ?? null); + + if ($column > mb_strlen($lineContent, 'UTF-8')) { + throw new \OutOfRangeException("Line $line doesn't have $column columns."); + } + + $result = $this->lineStarts[$line] + \strlen(mb_substr($lineContent, 0, $column, 'UTF-8')); + } if ($result > \strlen($this->string) || ($line + 1 < \count($this->lineStarts) && $result >= $this->lineStarts[$line + 1])) { throw new \OutOfRangeException("Line $line doesn't have $column columns."); diff --git a/vendor/scssphp/source-span/src/Util.php b/vendor/scssphp/source-span/src/Util.php index ccddb009c..2dc18b59a 100644 --- a/vendor/scssphp/source-span/src/Util.php +++ b/vendor/scssphp/source-span/src/Util.php @@ -2,8 +2,8 @@ namespace SourceSpan; -use League\Uri\BaseUri; use League\Uri\Contracts\UriInterface; +use League\Uri\Uri; /** * @internal @@ -349,10 +349,14 @@ final class Util private static function pathFromUri(UriInterface $uri): string { - if (\DIRECTORY_SEPARATOR === '\\') { - return BaseUri::from($uri)->windowsPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + if (!$uri instanceof Uri) { + $uri = Uri::new($uri); } - return BaseUri::from($uri)->unixPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + if (\DIRECTORY_SEPARATOR === '\\') { + return $uri->toWindowsPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + } + + return $uri->toUnixPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); } } diff --git a/vendor/symfony/filesystem/Filesystem.php b/vendor/symfony/filesystem/Filesystem.php index d08d99dda..e673a9c51 100644 --- a/vendor/symfony/filesystem/Filesystem.php +++ b/vendor/symfony/filesystem/Filesystem.php @@ -70,7 +70,7 @@ class Filesystem if ($originIsLocal) { // Like `cp`, preserve executable permission bits - self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); + self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0o111)); // Like `cp`, preserve the file modification time self::box('touch', $targetFile, filemtime($originFile)); @@ -87,7 +87,7 @@ class Filesystem * * @throws IOException On any directory creation failure */ - public function mkdir(string|iterable $dirs, int $mode = 0777): void + public function mkdir(string|iterable $dirs, int $mode = 0o777): void { foreach ($this->toIterable($dirs) as $dir) { if (is_dir($dir)) { @@ -208,7 +208,7 @@ class Filesystem * * @throws IOException When the change fails */ - public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false): void + public function chmod(string|iterable $files, int $mode, int $umask = 0o000, bool $recursive = false): void { foreach ($this->toIterable($files) as $file) { if (!self::box('chmod', $file, $mode & ~$umask)) { @@ -664,13 +664,13 @@ class Filesystem throw new IOException(\sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); } - self::box('chmod', $tmpFile, self::box('fileperms', $filename) ?: 0666 & ~umask()); + self::box('chmod', $tmpFile, self::box('fileperms', $filename) ?: 0o666 & ~umask()); $this->rename($tmpFile, $filename, true); } finally { if (file_exists($tmpFile)) { if ('\\' === \DIRECTORY_SEPARATOR && !is_writable($tmpFile)) { - self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0200); + self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0o200); } self::box('unlink', $tmpFile); diff --git a/vendor/symfony/filesystem/composer.json b/vendor/symfony/filesystem/composer.json index c781e55b1..42bbfa08a 100644 --- a/vendor/symfony/filesystem/composer.json +++ b/vendor/symfony/filesystem/composer.json @@ -21,7 +21,7 @@ "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^6.4|^7.0|^8.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" },