diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index 5c3934fee..48c3375ed 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -10,7 +10,7 @@ "mustangostang/spyc": "^0.6.3", "directorytree/ldaprecord": "^3.3", "twig/twig": "^3.0", - "stevenmaguire/oauth2-keycloak": "^4.0", + "stevenmaguire/oauth2-keycloak": "^6.1", "league/oauth2-client": "^2.7", "bacon/bacon-qr-code": "^2.0" }, diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index 4254deffb..1e28d8b12 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "50fb4a320500820e36f30eabc45222a0", + "content-hash": "c1b9909a3828030c80954fd2c04dfbee", "packages": [ { "name": "bacon/bacon-qr-code", @@ -62,32 +62,32 @@ }, { "name": "bshaffer/oauth2-server-php", - "version": "v1.11.1", + "version": "v1.14.2", "source": { "type": "git", "url": "https://github.com/bshaffer/oauth2-server-php.git", - "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa" + "reference": "1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/5a0c8000d4763b276919e2106f54eddda6bc50fa", - "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa", + "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6", + "reference": "1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=7.2" }, "require-dev": { - "aws/aws-sdk-php": "~2.8", - "firebase/php-jwt": "~2.2", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^4.0", - "predis/predis": "dev-master", - "thobbs/phpcassa": "dev-master" + "aws/aws-sdk-php": "^2.8", + "firebase/php-jwt": "^6.4", + "phpunit/phpunit": "^7.5||^8.0", + "predis/predis": "^1.1", + "thobbs/phpcassa": "dev-master", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", - "firebase/php-jwt": "~2.2 is required to use JWT features", + "firebase/php-jwt": "~v6.4 is required to use JWT features", "mongodb/mongodb": "^1.1 is required to use MongoDB storage", "predis/predis": "Required to use Redis storage", "thobbs/phpcassa": "Required to use Cassandra storage" @@ -118,22 +118,22 @@ ], "support": { "issues": "https://github.com/bshaffer/oauth2-server-php/issues", - "source": "https://github.com/bshaffer/oauth2-server-php/tree/master" + "source": "https://github.com/bshaffer/oauth2-server-php/tree/v1.14.2" }, - "time": "2018-12-04T00:29:32+00:00" + "time": "2025-03-28T21:13:45+00:00" }, { "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", "shasum": "" }, "require": { @@ -148,6 +148,11 @@ "phpunit/phpunit": "^10.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" @@ -173,7 +178,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1" }, "funding": [ { @@ -189,7 +194,7 @@ "type": "tidelift" } ], - "time": "2024-02-09T16:56:22+00:00" + "time": "2026-09-06T14:11:38+00:00" }, { "name": "dasprid/enum", @@ -243,32 +248,33 @@ }, { "name": "ddeboer/imap", - "version": "1.13.1", + "version": "1.19.0", "source": { "type": "git", "url": "https://github.com/ddeboer/imap.git", - "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e" + "reference": "30800b1cfeacc4add5bb418e40a8b6e95a8a04ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ddeboer/imap/zipball/8b772d04b1deadb5df13782fb78c4b648f77496e", - "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/30800b1cfeacc4add5bb418e40a8b6e95a8a04ac", + "reference": "30800b1cfeacc4add5bb418e40a8b6e95a8a04ac", "shasum": "" }, "require": { + "ext-dom": "*", "ext-iconv": "*", "ext-imap": "*", + "ext-libxml": "*", "ext-mbstring": "*", - "php": "^8.0.1" + "php": "~8.2.0 || ~8.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.4.0", - "laminas/laminas-mail": "^2.15.1", - "malukenho/mcbumpface": "^1.1.5", - "phpstan/phpstan": "^1.3.3", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpunit/phpunit": "^9.5.11" + "friendsofphp/php-cs-fixer": "^3.38.2", + "laminas/laminas-mail": "^2.25.1", + "phpstan/phpstan": "^1.10.43", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "phpunit/phpunit": "^10.4.2" }, "type": "library", "autoload": { @@ -302,7 +308,7 @@ ], "support": { "issues": "https://github.com/ddeboer/imap/issues", - "source": "https://github.com/ddeboer/imap/tree/1.13.1" + "source": "https://github.com/ddeboer/imap/tree/1.19.0" }, "funding": [ { @@ -314,28 +320,28 @@ "type": "github" } ], - "time": "2022-01-10T10:53:05+00:00" + "time": "2023-11-20T14:41:54+00:00" }, { "name": "directorytree/ldaprecord", - "version": "v3.8.5", + "version": "v3.8.6", "source": { "type": "git", "url": "https://github.com/DirectoryTree/LdapRecord.git", - "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65" + "reference": "a1cab9f078eb4756fc6fad58caea1e3d11cad0ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/00e5f088f8c4028d5f398783cccc2e8119a27a65", - "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65", + "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/a1cab9f078eb4756fc6fad58caea1e3d11cad0ec", + "reference": "a1cab9f078eb4756fc6fad58caea1e3d11cad0ec", "shasum": "" }, "require": { "ext-iconv": "*", "ext-json": "*", "ext-ldap": "*", - "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "*", "php": ">=8.1", "psr/log": "*", @@ -390,38 +396,49 @@ "type": "github" } ], - "time": "2025-10-06T02:22:34+00:00" + "time": "2026-03-19T20:55:30+00:00" }, { "name": "firebase/php-jwt", - "version": "v6.5.0", + "version": "v7.2.1", "source": { "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "0501f5729b9e2448516674aff07c7d1727869b8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", - "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/0501f5729b9e2448516674aff07c7d1727869b8e", + "reference": "0501f5729b9e2448516674aff07c7d1727869b8e", "shasum": "" }, "require": { - "php": "^7.4||^8.0" + "php": "^8.0" }, "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", - "phpspec/prophecy-phpunit": "^2.0", + "guzzlehttp/guzzle": "^7.4||^8.0", + "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", + "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0||^2.0", + "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0" }, "suggest": { "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" }, "type": "library", + "extra": { + "component": { + "id": "jwt", + "path": "Jwt", + "entry": "README.md", + "target": "googleapis/php-jwt.git" + } + }, "autoload": { "psr-4": { "Firebase\\JWT\\": "src" @@ -444,47 +461,51 @@ } ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", + "homepage": "https://github.com/googleapis/php-jwt", "keywords": [ "jwt", "php" ], "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.2.1" }, - "time": "2023-05-12T15:47:07+00:00" + "time": "2026-09-28T17:51:46+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "8.3.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "7d9b1f430bb65ac664e120af227bf9a795ad6333" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7d9b1f430bb65ac664e120af227bf9a795ad6333", + "reference": "7d9b1f430bb65ac664e120af227bf9a795ad6333", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", - "php": "^7.2.5 || ^8.0", + "guzzlehttp/promises": "^3.1", + "guzzlehttp/psr7": "^3.2", + "php": "^7.4 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "psr/http-factory": "^1.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "guzzle/client-integration-tests": "4.0.1", + "guzzlehttp/test-server": "^1.1", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -497,15 +518,9 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" } @@ -565,7 +580,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/8.3.1" }, "funding": [ { @@ -581,38 +596,38 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2026-10-25T21:35:34+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.2", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" + "reference": "094d0ec418eb3b1411ea271a180ec95cae6ae933" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "url": "https://api.github.com/repos/guzzle/promises/zipball/094d0ec418eb3b1411ea271a180ec95cae6ae933", + "reference": "094d0ec418eb3b1411ea271a180ec95cae6ae933", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.34" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -649,7 +664,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" + "source": "https://github.com/guzzle/promises/tree/3.1.0" }, "funding": [ { @@ -665,36 +680,39 @@ "type": "tidelift" } ], - "time": "2022-08-28T14:55:35+00:00" + "time": "2026-10-18T12:48:16+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.4.5", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66" + "reference": "cdc139396207a5eba10c242d241fb014cb098390" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66", - "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/cdc139396207a5eba10c242d241fb014cb098390", + "reference": "cdc139396207a5eba10c242d241fb014cb098390", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -765,7 +783,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.5" + "source": "https://github.com/guzzle/psr7/tree/3.2.0" }, "funding": [ { @@ -781,39 +799,44 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:00:45+00:00" + "time": "2026-10-17T23:57:19+00:00" }, { "name": "illuminate/collections", - "version": "v10.49.0", + "version": "v12.70.2", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232" + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", - "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "url": "https://api.github.com/repos/illuminate/collections/zipball/83313b009c4afb6f02dbc090bdb67809756eefa2", + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2", "shasum": "" }, "require": { - "illuminate/conditionable": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "php": "^8.1" + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^6.2)." + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { "files": [ + "functions.php", "helpers.php" ], "psr-4": { @@ -836,29 +859,29 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-09-08T19:05:53+00:00" + "time": "2026-03-11T14:13:25+00:00" }, { "name": "illuminate/conditionable", - "version": "v10.49.0", + "version": "v12.70.2", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", - "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f" + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/conditionable/zipball/47c700320b7a419f0d188d111f3bbed978fcbd3f", - "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49", + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49", "shasum": "" }, "require": { - "php": "^8.0.2" + "php": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -882,31 +905,31 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-24T11:47:24+00:00" + "time": "2025-05-13T15:08:45+00:00" }, { "name": "illuminate/contracts", - "version": "v10.44.0", + "version": "v12.70.2", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac" + "reference": "c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", - "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5", + "reference": "c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/simple-cache": "^1.0|^2.0|^3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -930,29 +953,29 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-01-15T18:52:32+00:00" + "time": "2026-06-09T13:20:54+00:00" }, { "name": "illuminate/macroable", - "version": "v10.49.0", + "version": "v12.70.2", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", - "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", - "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -976,39 +999,35 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-06-05T12:46:42+00:00" + "time": "2026-03-30T19:05:19+00:00" }, { "name": "league/oauth2-client", - "version": "2.7.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0 || ^7.0", - "paragonie/random_compat": "^1 || ^2 || ^9.99", - "php": "^5.6 || ^7.0 || ^8.0" + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.8.2 || ^8.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.6.3 || ^3.0", + "php": "^7.1 || >=8.0.0 <8.6.0" }, "require-dev": { "mockery/mockery": "^1.3.5", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", - "squizlabs/php_codesniffer": "^2.3 || ^3.0" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.0.x-dev" - } - }, "autoload": { "psr-4": { "League\\OAuth2\\Client\\": "src/" @@ -1044,22 +1063,22 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-client/issues", - "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.1" }, - "time": "2023-04-16T18:19:15+00:00" + "time": "2026-09-16T13:14:31+00:00" }, { "name": "matthiasmullie/minify", - "version": "1.3.66", + "version": "1.3.75", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6" + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", - "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671", + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671", "shasum": "" }, "require": { @@ -1068,8 +1087,8 @@ "php": ">=5.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.0", - "matthiasmullie/scrapbook": "dev-master", + "friendsofphp/php-cs-fixer": ">=2.0", + "matthiasmullie/scrapbook": ">=1.3", "phpunit/phpunit": ">=4.8" }, "suggest": { @@ -1093,12 +1112,12 @@ { "name": "Matthias Mullie", "email": "minify@mullie.eu", - "homepage": "http://www.mullie.eu", + "homepage": "https://www.mullie.eu", "role": "Developer" } ], "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", - "homepage": "http://www.minifier.org", + "homepage": "https://github.com/matthiasmullie/minify", "keywords": [ "JS", "css", @@ -1108,23 +1127,15 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.66" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.75" }, "funding": [ { - "url": "https://github.com/[user1", - "type": "github" - }, - { - "url": "https://github.com/matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g.", - "type": "github" - }, - { - "url": "https://github.com/user2", + "url": "https://github.com/matthiasmullie", "type": "github" } ], - "time": "2021-01-06T15:18:10+00:00" + "time": "2025-06-25T09:56:19+00:00" }, { "name": "matthiasmullie/path-converter", @@ -1184,7 +1195,7 @@ "version": "0.6.3", "source": { "type": "git", - "url": "git@github.com:mustangostang/spyc.git", + "url": "https://github.com/mustangostang/spyc.git", "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" }, "dist": { @@ -1227,56 +1238,55 @@ "yaml", "yml" ], + "support": { + "issues": "https://github.com/mustangostang/spyc/issues", + "source": "https://github.com/mustangostang/spyc/tree/0.6.3" + }, "time": "2019-09-10T13:16:29+00:00" }, { "name": "nesbot/carbon", - "version": "2.72.3", + "version": "3.15.1", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83", - "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ce0e5636fae7d1e99856eb70cae4c719a284aa08", + "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "*", + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", - "php": "^7.1.8 || ^8.0", + "php": "^8.1", "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + "symfony/polyfill-time": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" }, "provide": { "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", - "doctrine/orm": "^2.7 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.0", - "kylekatarnls/multi-tester": "^2.0", - "ondrejmirtes/better-reflection": "*", - "phpmd/phpmd": "^2.9", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.99 || ^1.7.14", - "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", - "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", - "squizlabs/php_codesniffer": "^3.4" + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^3.0@dev", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, "bin": [ "bin/carbon" ], "type": "library", "extra": { - "branch-alias": { - "dev-3.x": "3.x-dev", - "dev-master": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -1286,6 +1296,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -1309,16 +1323,16 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", - "issues": "https://github.com/briannesbitt/Carbon/issues", - "source": "https://github.com/briannesbitt/Carbon" + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { @@ -1334,70 +1348,20 @@ "type": "tidelift" } ], - "time": "2024-01-25T10:35:09+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "time": "2020-10-15T08:29:30+00:00" + "time": "2026-10-25T14:32:48+00:00" }, { "name": "php-mime-mail-parser/php-mime-mail-parser", - "version": "7.0.0", + "version": "7.1.2", "source": { "type": "git", "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", - "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d" + "reference": "81d84bbc6a73b913110e155e98059526825c7ee3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/9d09a017f3f103fec8456211a4a538b80e0eca0d", - "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d", + "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/81d84bbc6a73b913110e155e98059526825c7ee3", + "reference": "81d84bbc6a73b913110e155e98059526825c7ee3", "shasum": "" }, "require": { @@ -1410,7 +1374,7 @@ }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^8.0", + "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", @@ -1467,7 +1431,7 @@ ], "support": { "issues": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/issues", - "source": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/tree/7.0.0" + "source": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/tree/7.1.2" }, "funding": [ { @@ -1475,20 +1439,20 @@ "type": "github" } ], - "time": "2021-02-25T17:21:57+00:00" + "time": "2022-04-16T07:56:53+00:00" }, { "name": "phpmailer/phpmailer", - "version": "v6.6.0", + "version": "v6.12.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1" + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e43bac82edc26ca04b36143a48bde1c051cfd5b1", - "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", "shasum": "" }, "require": { @@ -1498,22 +1462,25 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3.1", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, "type": "library", "autoload": { @@ -1545,7 +1512,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.0" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" }, "funding": [ { @@ -1553,7 +1520,7 @@ "type": "github" } ], - "time": "2022-02-28T15:31:21+00:00" + "time": "2025-10-15T16:49:08+00:00" }, { "name": "psr/clock", @@ -1658,21 +1625,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -1692,7 +1659,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -1704,27 +1671,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -1744,10 +1711,10 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1759,31 +1726,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1798,7 +1765,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -1812,22 +1779,22 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1862,22 +1829,22 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { @@ -1886,7 +1853,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1913,66 +1880,22 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2021-10-29T13:22:09+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "robthree/twofactorauth", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/RobThree/TwoFactorAuth.git", - "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b" + "reference": "65681de5a324eae05140ac58b08648a60212afc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5afcb45282f1c75562a48d479ecd1732c9bdb11b", - "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/65681de5a324eae05140ac58b08648a60212afc0", + "reference": "65681de5a324eae05140ac58b08648a60212afc0", "shasum": "" }, "require": { @@ -2029,7 +1952,7 @@ "type": "github" } ], - "time": "2021-10-20T12:19:55+00:00" + "time": "2022-03-22T16:11:07+00:00" }, { "name": "soundasleep/html2text", @@ -2088,25 +2011,26 @@ }, { "name": "stevenmaguire/oauth2-keycloak", - "version": "4.0.0", + "version": "6.1.1", "source": { "type": "git", "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", - "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d" + "reference": "31bb3b1fa15b444212ed43facc898fafc7c2707a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", - "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/31bb3b1fa15b444212ed43facc898fafc7c2707a", + "reference": "31bb3b1fa15b444212ed43facc898fafc7c2707a", "shasum": "" }, "require": { - "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0", - "league/oauth2-client": "^2.0", - "php": "~7.2 || ~8.0" + "firebase/php-jwt": "^7.0", + "league/oauth2-client": "^2.8", + "php": "^8.0" }, "require-dev": { - "mockery/mockery": "~1.5.0", + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "~9.6.4", "squizlabs/php_codesniffer": "~3.7.0" }, @@ -2143,22 +2067,100 @@ ], "support": { "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", - "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0" + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/6.1.1" }, - "time": "2023-03-14T09:43:47+00:00" + "time": "2026-03-30T07:32:03+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "name": "symfony/clock", + "version": "v7.4.21", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "url": "https://github.com/symfony/clock.git", + "reference": "07437f612dca89c1894c1db04491002b1e798f03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/clock/zipball/07437f612dca89c1894c1db04491002b1e798f03", + "reference": "07437f612dca89c1894c1db04491002b1e798f03", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-10-12T14:31:36+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -2166,12 +2168,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { @@ -2196,7 +2198,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -2207,29 +2209,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.24.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2239,12 +2245,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2278,7 +2281,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -2289,29 +2292,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.43.3", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/122889ed24dc000165bcbfd77ec412f15cbdfc6a", + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2322,8 +2330,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2358,7 +2366,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.3" }, "funding": [ { @@ -2369,35 +2377,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2026-10-28T09:35:49+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2438,7 +2450,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { @@ -2449,25 +2461,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { - "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "name": "symfony/polyfill-php82", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/843bbe8b2a7934307b749751eede0f111f5d1cfc", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc", "shasum": "" }, "require": { @@ -2476,8 +2492,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -2485,7 +2501,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php82\\": "" }, "classmap": [ "Resources/stubs" @@ -2505,7 +2521,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2514,7 +2530,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.43.0" }, "funding": [ { @@ -2525,60 +2541,388 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { - "name": "symfony/translation", - "version": "v6.4.3", + "name": "symfony/polyfill-php83", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "637c51191b6b184184bbf98937702bcf554f7d04" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/637c51191b6b184184bbf98937702bcf554f7d04", - "reference": "637c51191b6b184184bbf98937702bcf554f7d04", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/80ccff923a8d61f73ebe9da0aa94e25232f004ce", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:50:05+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-23T22:01:09+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "symfony/polyfill-time", + "version": "v1.42.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-time.git", + "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-time/zipball/ac9f1ed802dd58f37e720388130c088434aa09fc", + "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Time\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Philippe Nyamagana Butera", + "email": "nyamsprod@gmail.com" + }, + { + "name": "Tim Düsterhus", + "email": "tim@tideways-gmbh.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Time API added to PHP 8.6", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "duration", + "polyfill", + "portable", + "shim", + "time" + ], + "support": { + "source": "https://github.com/symfony/polyfill-time/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-07T13:08:13+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/2ee1e4a3b32a528a642babe041ff7c440b213b4b", + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b", + "shasum": "" + }, + "require": { + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5|^3.0" + "symfony/translation-contracts": "^2.5.3|^3.3" }, "conflict": { - "symfony/config": "<5.4", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.4", "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<5.4", - "symfony/yaml": "<5.4" + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2609,7 +2953,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.3" + "source": "https://github.com/symfony/translation/tree/v7.4.17" }, "funding": [ { @@ -2620,25 +2964,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-01-29T13:11:52+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.4.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "06450585bf65e978026bda220cdebca3f867fde7" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", - "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -2646,12 +2994,12 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { @@ -2687,7 +3035,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -2698,35 +3046,40 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "twig/twig", - "version": "v3.14.0", + "version": "v3.30.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + "reference": "8c737079b726af72ff8ef3c595be9f6a810ea1ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8c737079b726af72ff8ef3c595be9f6a810ea1ef", + "reference": "8c737079b726af72ff8ef3c595be9f6a810ea1ef", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php81": "^1.29" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, @@ -2770,7 +3123,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + "source": "https://github.com/twigphp/Twig/tree/v3.30.0" }, "funding": [ { @@ -2782,7 +3135,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T17:55:12+00:00" + "time": "2026-09-25T13:20:01+00:00" } ], "packages-dev": [], @@ -2793,5 +3146,5 @@ "prefer-lowest": false, "platform": {}, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/data/web/inc/lib/vendor/autoload.php b/data/web/inc/lib/vendor/autoload.php index a0fb8f4ac..1fc87521d 100644 --- a/data/web/inc/lib/vendor/autoload.php +++ b/data/web/inc/lib/vendor/autoload.php @@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) { echo $err; } } - trigger_error( - $err, - E_USER_ERROR - ); + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/data/web/inc/lib/vendor/bin/minifycss b/data/web/inc/lib/vendor/bin/minifycss index 0604366b1..51118392c 100755 --- a/data/web/inc/lib/vendor/bin/minifycss +++ b/data/web/inc/lib/vendor/bin/minifycss @@ -12,6 +12,7 @@ namespace Composer; +$GLOBALS['_composer_bin_dir'] = __DIR__; $GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; if (PHP_VERSION_ID < 80000) { @@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) { { private $handle; private $position; + private $realpath; public function stream_open($path, $mode, $options, &$opened_path) { - // get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution - $opened_path = substr($path, 21); - $opened_path = realpath($opened_path) ?: $opened_path; - $this->handle = fopen($opened_path, $mode); + // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution + $opened_path = substr($path, 17); + $this->realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); $this->position = 0; - // remove all traces of this stream wrapper once it has been used - stream_wrapper_unregister('composer-bin-proxy'); - return (bool) $this->handle; } @@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) { return $operation ? flock($this->handle, $operation) : true; } + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + public function stream_tell() { return $this->position; @@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) { public function stream_stat() { - return fstat($this->handle); + return array(); } public function stream_set_option($option, $arg1, $arg2) { return true; } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } } } - if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { - include("composer-bin-proxy://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'); - exit(0); + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'); } } -include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'; +return include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'; diff --git a/data/web/inc/lib/vendor/bin/minifyjs b/data/web/inc/lib/vendor/bin/minifyjs index f8ee9123a..3ef06b043 100755 --- a/data/web/inc/lib/vendor/bin/minifyjs +++ b/data/web/inc/lib/vendor/bin/minifyjs @@ -12,6 +12,7 @@ namespace Composer; +$GLOBALS['_composer_bin_dir'] = __DIR__; $GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; if (PHP_VERSION_ID < 80000) { @@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) { { private $handle; private $position; + private $realpath; public function stream_open($path, $mode, $options, &$opened_path) { - // get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution - $opened_path = substr($path, 21); - $opened_path = realpath($opened_path) ?: $opened_path; - $this->handle = fopen($opened_path, $mode); + // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution + $opened_path = substr($path, 17); + $this->realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); $this->position = 0; - // remove all traces of this stream wrapper once it has been used - stream_wrapper_unregister('composer-bin-proxy'); - return (bool) $this->handle; } @@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) { return $operation ? flock($this->handle, $operation) : true; } + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + public function stream_tell() { return $this->position; @@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) { public function stream_stat() { - return fstat($this->handle); + return array(); } public function stream_set_option($option, $arg1, $arg2) { return true; } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } } } - if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { - include("composer-bin-proxy://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'); - exit(0); + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'); } } -include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'; +return include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/.github/workflows/tests.yml b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/.github/workflows/tests.yml new file mode 100644 index 000000000..fe13e48b8 --- /dev/null +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/.github/workflows/tests.yml @@ -0,0 +1,74 @@ +name: Test Suite +on: + push: + branches: + - main + pull_request: +jobs: + test: + runs-on: ubuntu-latest + services: + redis: + image: redis + ports: + - 6379:6379 + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + mongodb: + image: mongo + ports: + - 27017:27017 + myriadb: + image: mariadb + env: + MYSQL_ROOT_PASSWORD: root + ports: + - 3808:3808 + - 3306:3306 + postgres: + image: postgres + env: + POSTGRES_DB: oauth2_server_php + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: --health-cmd="pg_isready -h localhost" --health-interval=10s --health-timeout=5s --health-retries=5 + strategy: + matrix: + php: [ 7.2, 7.3, 7.4, "8.0", 8.1, 8.2 ] + name: "PHP ${{ matrix.php }} Unit Test" + steps: + - uses: actions/checkout@v2 + - uses: codecov/codecov-action@v1 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mongodb, mbstring, intl, redis, pdo_mysql + - name: Install composer dependencies + uses: nick-invision/retry@v1 + with: + timeout_minutes: 10 + max_attempts: 3 + command: composer install + - name: Run PHPUnit + run: vendor/bin/phpunit -v + phpstan: + name: "PHPStan" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + - name: Install composer dependencies + uses: nick-invision/retry@v1 + with: + timeout_minutes: 10 + max_attempts: 3 + command: composer install + - name: Run PHPStan + run: | + composer require phpstan/phpstan + vendor/bin/phpstan analyse --level=0 src/ diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md index 117743d4f..4ecadbe7a 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/README.md @@ -1,8 +1,6 @@ oauth2-server-php ================= -[![Build Status](https://travis-ci.org/bshaffer/oauth2-server-php.svg?branch=master)](https://travis-ci.org/bshaffer/oauth2-server-php) - -[![Total Downloads](https://poser.pugx.org/bshaffer/oauth2-server-php/downloads.png)](https://packagist.org/packages/bshaffer/oauth2-server-php) +[![Total Downloads](https://poser.pugx.org/bshaffer/oauth2-server-php/downloads)](https://packagist.org/packages/bshaffer/oauth2-server-php) View the [complete documentation](https://bshaffer.github.io/oauth2-server-php-docs/) diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json index 272d20027..50a950774 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/composer.json @@ -16,21 +16,21 @@ "psr-0": { "OAuth2": "src/" } }, "require":{ - "php":">=5.3.9" + "php":">=7.2" }, "require-dev": { - "phpunit/phpunit": "^4.0", - "aws/aws-sdk-php": "~2.8", - "firebase/php-jwt": "~2.2", - "predis/predis": "dev-master", + "phpunit/phpunit": "^7.5||^8.0", + "aws/aws-sdk-php": "^2.8", + "firebase/php-jwt": "^6.4", + "predis/predis": "^1.1", "thobbs/phpcassa": "dev-master", - "mongodb/mongodb": "^1.1" + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "predis/predis": "Required to use Redis storage", "thobbs/phpcassa": "Required to use Cassandra storage", "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", - "firebase/php-jwt": "~2.2 is required to use JWT features", + "firebase/php-jwt": "~v6.4 is required to use JWT features", "mongodb/mongodb": "^1.1 is required to use MongoDB storage" } } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php index 4ec08cbdd..b025db34c 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Autoloader.php @@ -39,7 +39,7 @@ class Autoloader * Handles autoloading of classes. * * @param string $class - A class name. - * @return boolean - Returns true if the class has been loaded + * @return void */ public function autoload($class) { diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/HttpBasic.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/HttpBasic.php index ef6120300..0d8007178 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/HttpBasic.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ClientAssertionType/HttpBasic.php @@ -112,7 +112,7 @@ class HttpBasic implements ClientAssertionTypeInterface * * @ingroup oauth2_section_2 */ - public function getClientCredentials(RequestInterface $request, ResponseInterface $response = null) + public function getClientCredentials(RequestInterface $request, ?ResponseInterface $response = null) { if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) { return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW')); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php index 4bafb1d24..181f884a6 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/AuthorizeController.php @@ -78,7 +78,7 @@ class AuthorizeController implements AuthorizeControllerInterface * ); * @endcode */ - public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null) + public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ?ScopeInterface $scopeUtil = null) { $this->clientStorage = $clientStorage; $this->responseTypes = $responseTypes; @@ -87,6 +87,7 @@ class AuthorizeController implements AuthorizeControllerInterface 'enforce_state' => true, 'require_exact_redirect_uri' => true, 'redirect_status_code' => 302, + 'enforce_pkce' => false, ), $config); if (is_null($scopeUtil)) { diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php index 926f90fda..2a5d4b4e3 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceController.php @@ -47,7 +47,7 @@ class ResourceController implements ResourceControllerInterface * @param array $config * @param ScopeInterface $scopeUtil */ - public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ScopeInterface $scopeUtil = null) + public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ?ScopeInterface $scopeUtil = null) { $this->tokenType = $tokenType; $this->tokenStorage = $tokenStorage; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php index 9d005143d..0e847ca61 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/ResourceControllerInterface.php @@ -13,7 +13,7 @@ use OAuth2\ResponseInterface; * @code * if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) { * $response->send(); // authorization failed - * exit(); + * die(); * } * return json_encode($resource); // valid token! Send the stuff! * @endcode diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php index 7fdaf85a6..1e21b5f67 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Controller/TokenController.php @@ -54,7 +54,7 @@ class TokenController implements TokenControllerInterface * @param ScopeInterface $scopeUtil * @throws InvalidArgumentException */ - public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null) + public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ?ClientAssertionTypeInterface $clientAssertionType = null, ?ScopeInterface $scopeUtil = null) { if (is_null($clientAssertionType)) { foreach ($grantTypes as $grantType) { diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/FirebaseJwt.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/FirebaseJwt.php index 1b527e0a0..8524bb32c 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/FirebaseJwt.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Encryption/FirebaseJwt.php @@ -2,6 +2,9 @@ namespace OAuth2\Encryption; +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + /** * Bridge file to use the firebase/php-jwt package for JWT encoding and decoding. * @author Francis Chuang @@ -10,26 +13,36 @@ class FirebaseJwt implements EncryptionInterface { public function __construct() { - if (!class_exists('\JWT')) { + if (!class_exists(JWT::class)) { throw new \ErrorException('firebase/php-jwt must be installed to use this feature. You can do this by running "composer require firebase/php-jwt"'); } } public function encode($payload, $key, $alg = 'HS256', $keyId = null) { - return \JWT::encode($payload, $key, $alg, $keyId); + return JWT::encode($payload, $key, $alg, $keyId); } public function decode($jwt, $key = null, $allowedAlgorithms = null) { try { - //Maintain BC: Do not verify if no algorithms are passed in. if (!$allowedAlgorithms) { - $key = null; + $tks = \explode('.', $jwt); + if (\count($tks) === 3) { + [$headb64] = $tks; + $headerRaw = JWT::urlsafeB64Decode($headb64); + if (($header = JWT::jsonDecode($headerRaw))) { + $key = new Key($key, $header->alg); + } + } + } elseif(is_array($allowedAlgorithms)) { + $key = new Key($key, $allowedAlgorithms[0]); + } else { + $key = new Key($key, $allowedAlgorithms); } - return (array)\JWT::decode($jwt, $key, $allowedAlgorithms); + return (array) JWT::decode($jwt, $key); } catch (\Exception $e) { return false; } @@ -37,11 +50,11 @@ class FirebaseJwt implements EncryptionInterface public function urlSafeB64Encode($data) { - return \JWT::urlsafeB64Encode($data); + return JWT::urlsafeB64Encode($data); } public function urlSafeB64Decode($b64) { - return \JWT::urlsafeB64Decode($b64); + return JWT::urlsafeB64Decode($b64); } } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php index 784f6b3a3..5bcb4f253 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/AuthorizationCode.php @@ -84,6 +84,41 @@ class AuthorizationCode implements GrantTypeInterface return false; } + if (isset($authCode['code_challenge']) && $authCode['code_challenge']) { + if (!($code_verifier = $request->request('code_verifier'))) { + $response->setError(400, 'code_verifier_missing', "The PKCE code verifier parameter is required."); + + return false; + } + // Validate code_verifier according to RFC-7636 + // @see: https://tools.ietf.org/html/rfc7636#section-4.1 + if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $code_verifier) !== 1) { + $response->setError(400, 'code_verifier_invalid', "The PKCE code verifier parameter is invalid."); + + return false; + } + $code_verifier = $request->request('code_verifier'); + switch ($authCode['code_challenge_method']) { + case 'S256': + $code_verifier_hashed = strtr(rtrim(base64_encode(hash('sha256', $code_verifier, true)), '='), '+/', '-_'); + break; + + case 'plain': + $code_verifier_hashed = $code_verifier; + break; + + default: + $response->setError(400, 'code_challenge_method_invalid', "Unknown PKCE code challenge method."); + + return FALSE; + } + if ($code_verifier_hashed !== $authCode['code_challenge']) { + $response->setError(400, 'code_verifier_mismatch', "The PKCE code verifier parameter does not match the code challenge."); + + return FALSE; + } + } + if (!isset($authCode['code'])) { $authCode['code'] = $code; // used to expire the code after the access token is granted } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/JwtBearer.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/JwtBearer.php index 62c1efabd..6d5a9f963 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/JwtBearer.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/GrantType/JwtBearer.php @@ -37,7 +37,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface * @param EncryptionInterface|JWT $jwtUtil - OPTONAL The class used to decode, encode and verify JWTs. * @param array $config */ - public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null, array $config = array()) + public function __construct(JwtBearerInterface $storage, $audience, ?EncryptionInterface $jwtUtil = null, array $config = array()) { $this->storage = $storage; $this->audience = $audience; @@ -46,13 +46,13 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface $jwtUtil = new Jwt(); } - $this->config = array_merge(array( + $config = array_merge(array( 'allowed_algorithms' => array('RS256', 'RS384', 'RS512') ), $config); $this->jwtUtil = $jwtUtil; - $this->allowedAlgorithms = $this->config['allowed_algorithms']; + $this->allowedAlgorithms = $config['allowed_algorithms']; } /** @@ -127,7 +127,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface } // Check expiration - if (ctype_digit($jwt['exp'])) { + if (ctype_digit((string)$jwt['exp'])) { if ($jwt['exp'] <= time()) { $response->setError(400, 'invalid_grant', "JWT has expired"); @@ -141,7 +141,7 @@ class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface // Check the not before time if ($notBefore = $jwt['nbf']) { - if (ctype_digit($notBefore)) { + if (ctype_digit((string)$notBefore)) { if ($notBefore > time()) { $response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time"); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php index 54c5f9a63..52e183bb3 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/AuthorizeController.php @@ -16,6 +16,16 @@ class AuthorizeController extends BaseAuthorizeController implements AuthorizeCo */ private $nonce; + /** + * @var mixed + */ + protected $code_challenge; + + /** + * @var mixed + */ + protected $code_challenge_method; + /** * Set not authorized response * @@ -65,6 +75,10 @@ class AuthorizeController extends BaseAuthorizeController implements AuthorizeCo // add the nonce to return with the redirect URI $params['nonce'] = $this->nonce; + // Add PKCE code challenge. + $params['code_challenge'] = $this->code_challenge; + $params['code_challenge_method'] = $this->code_challenge_method; + return $params; } @@ -90,6 +104,32 @@ class AuthorizeController extends BaseAuthorizeController implements AuthorizeCo $this->nonce = $nonce; + $code_challenge = $request->query('code_challenge'); + $code_challenge_method = $request->query('code_challenge_method'); + + if ($this->config['enforce_pkce']) { + if (!$code_challenge) { + $response->setError(400, 'missing_code_challenge', 'This application requires you provide a PKCE code challenge'); + + return false; + } + + if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $code_challenge) !== 1) { + $response->setError(400, 'invalid_code_challenge', 'The PKCE code challenge supplied is invalid'); + + return false; + } + + if (!in_array($code_challenge_method, array('plain', 'S256'), true)) { + $response->setError(400, 'missing_code_challenge_method', 'This application requires you specify a PKCE code challenge method'); + + return false; + } + } + + $this->code_challenge = $code_challenge; + $this->code_challenge_method = $code_challenge_method; + return true; } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoController.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoController.php index c489b7af3..e6f8fd77b 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoController.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Controller/UserInfoController.php @@ -30,7 +30,7 @@ class UserInfoController extends ResourceController implements UserInfoControlle * @param array $config * @param ScopeInterface $scopeUtil */ - public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, UserClaimsInterface $userClaimsStorage, $config = array(), ScopeInterface $scopeUtil = null) + public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, UserClaimsInterface $userClaimsStorage, $config = array(), ?ScopeInterface $scopeUtil = null) { parent::__construct($tokenType, $tokenStorage, $config, $scopeUtil); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php index b8ad41ffb..19e04104d 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/AuthorizationCode.php @@ -31,9 +31,9 @@ class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCo // build the URL to redirect to $result = array('query' => array()); - $params += array('scope' => null, 'state' => null, 'id_token' => null); + $params += array('scope' => null, 'state' => null, 'id_token' => null, 'code_challenge' => null, 'code_challenge_method' => null); - $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token']); + $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token'], $params['code_challenge'], $params['code_challenge_method']); if (isset($params['state'])) { $result['query']['state'] = $params['state']; @@ -56,10 +56,10 @@ class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCo * @see http://tools.ietf.org/html/rfc6749#section-4 * @ingroup oauth2_section_4 */ - public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null) + public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { $code = $this->generateAuthorizationCode(); - $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token); + $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token, $code_challenge, $code_challenge_method); return $code; } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdToken.php index 55e446074..98973f9e9 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdToken.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/ResponseType/IdToken.php @@ -38,7 +38,7 @@ class IdToken implements IdTokenInterface * @param EncryptionInterface $encryptionUtil * @throws LogicException */ - public function __construct(UserClaimsInterface $userClaimsStorage, PublicKeyInterface $publicKeyStorage, array $config = array(), EncryptionInterface $encryptionUtil = null) + public function __construct(UserClaimsInterface $userClaimsStorage, PublicKeyInterface $publicKeyStorage, array $config = array(), ?EncryptionInterface $encryptionUtil = null) { $this->userClaimsStorage = $userClaimsStorage; $this->publicKeyStorage = $publicKeyStorage; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php index 446cec928..8e0988ff4 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/OpenID/Storage/AuthorizationCodeInterface.php @@ -33,5 +33,5 @@ interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface * * @ingroup oauth2_section_4 */ - public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null); + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null); } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php index f547bf6e8..56a681a62 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Request.php @@ -34,7 +34,7 @@ class Request implements RequestInterface * * @api */ - public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null) + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, ?array $headers = null) { $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content, $headers); } @@ -55,7 +55,7 @@ class Request implements RequestInterface * * @api */ - public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, array $headers = null) + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null, ?array $headers = null) { $this->request = $request; $this->query = $query; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Response.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Response.php index 88c1ad5f7..be1eaa2ac 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Response.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Response.php @@ -140,11 +140,10 @@ class Response implements ResponseInterface } /** - * @param int $statusCode * @param string $text * @throws InvalidArgumentException */ - public function setStatusCode($statusCode, $text = null) + public function setStatusCode(int $statusCode, $text = null) { $this->statusCode = (int) $statusCode; if ($this->isInvalid()) { diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php index fe920864a..df4fa48f5 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseInterface.php @@ -20,10 +20,7 @@ interface ResponseInterface */ public function addHttpHeaders(array $httpHeaders); - /** - * @param int $statusCode - */ - public function setStatusCode($statusCode); + public function setStatusCode(int $statusCode); /** * @param int $statusCode diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php index e836a3447..8b5aec14a 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php @@ -38,7 +38,7 @@ class AccessToken implements AccessTokenInterface * ); * @endcode */ - public function __construct(AccessTokenStorageInterface $tokenStorage, RefreshTokenInterface $refreshStorage = null, array $config = array()) + public function __construct(AccessTokenStorageInterface $tokenStorage, ?RefreshTokenInterface $refreshStorage = null, array $config = array()) { $this->tokenStorage = $tokenStorage; $this->refreshStorage = $refreshStorage; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php index b92c73cda..12a9f8c38 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AuthorizationCode.php @@ -26,9 +26,9 @@ class AuthorizationCode implements AuthorizationCodeInterface // build the URL to redirect to $result = array('query' => array()); - $params += array('scope' => null, 'state' => null); + $params += array('scope' => null, 'state' => null, 'code_challenge' => null, 'code_challenge_method' => null); - $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope']); + $result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['code_challenge'], $params['code_challenge_method']); if (isset($params['state'])) { $result['query']['state'] = $params['state']; @@ -53,10 +53,10 @@ class AuthorizationCode implements AuthorizationCodeInterface * @see http://tools.ietf.org/html/rfc6749#section-4 * @ingroup oauth2_section_4 */ - public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null) + public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $code_challenge = null, $code_challenge_method = null) { $code = $this->generateAuthorizationCode(); - $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope); + $this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, null, $code_challenge, $code_challenge_method); return $code; } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php index 0ee3708aa..13a24a56d 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/JwtAccessToken.php @@ -26,7 +26,7 @@ class JwtAccessToken extends AccessToken * or just the token ID is stored * @param EncryptionInterface $encryptionUtil - */ - public function __construct(PublicKeyInterface $publicKeyStorage = null, AccessTokenStorageInterface $tokenStorage = null, RefreshTokenInterface $refreshStorage = null, array $config = array(), EncryptionInterface $encryptionUtil = null) + public function __construct(?PublicKeyInterface $publicKeyStorage = null, ?AccessTokenStorageInterface $tokenStorage = null, ?RefreshTokenInterface $refreshStorage = null, array $config = array(), ?EncryptionInterface $encryptionUtil = null) { $this->publicKeyStorage = $publicKeyStorage; $config = array_merge(array( diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Server.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Server.php index cf040c2bc..e5716358d 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Server.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Server.php @@ -150,7 +150,7 @@ class Server implements ResourceControllerInterface, * * @ingroup oauth2_section_7 */ - public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), TokenTypeInterface $tokenType = null, ScopeInterface $scopeUtil = null, ClientAssertionTypeInterface $clientAssertionType = null) + public function __construct($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), ?TokenTypeInterface $tokenType = null, ?ScopeInterface $scopeUtil = null, ?ClientAssertionTypeInterface $clientAssertionType = null) { $storage = is_array($storage) ? $storage : array($storage); $this->storages = array(); @@ -172,6 +172,7 @@ class Server implements ResourceControllerInterface, 'enforce_state' => true, 'require_exact_redirect_uri' => true, 'allow_implicit' => false, + 'enforce_pkce' => false, 'allow_credentials_in_request_body' => true, 'allow_public_clients' => true, 'always_issue_new_refresh_token' => false, @@ -288,7 +289,7 @@ class Server implements ResourceControllerInterface, * * @see http://openid.net/specs/openid-connect-core-1_0.html#UserInfo */ - public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response = null) + public function handleUserInfoRequest(RequestInterface $request, ?ResponseInterface $response = null) { $this->response = is_null($response) ? new Response() : $response; $this->getUserInfoController()->handleUserInfoRequest($request, $this->response); @@ -314,7 +315,7 @@ class Server implements ResourceControllerInterface, * * @ingroup oauth2_section_4 */ - public function handleTokenRequest(RequestInterface $request, ResponseInterface $response = null) + public function handleTokenRequest(RequestInterface $request, ?ResponseInterface $response = null) { $this->response = is_null($response) ? new Response() : $response; $this->getTokenController()->handleTokenRequest($request, $this->response); @@ -327,7 +328,7 @@ class Server implements ResourceControllerInterface, * @param ResponseInterface $response - Response object * @return mixed */ - public function grantAccessToken(RequestInterface $request, ResponseInterface $response = null) + public function grantAccessToken(RequestInterface $request, ?ResponseInterface $response = null) { $this->response = is_null($response) ? new Response() : $response; $value = $this->getTokenController()->grantAccessToken($request, $this->response); @@ -345,7 +346,7 @@ class Server implements ResourceControllerInterface, * @param ResponseInterface $response * @return Response|ResponseInterface */ - public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response = null) + public function handleRevokeRequest(RequestInterface $request, ?ResponseInterface $response = null) { $this->response = is_null($response) ? new Response() : $response; $this->getTokenController()->handleRevokeRequest($request, $this->response); @@ -407,7 +408,7 @@ class Server implements ResourceControllerInterface, * * @ingroup oauth2_section_3 */ - public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response = null) + public function validateAuthorizeRequest(RequestInterface $request, ?ResponseInterface $response = null) { $this->response = is_null($response) ? new Response() : $response; $value = $this->getAuthorizeController()->validateAuthorizeRequest($request, $this->response); @@ -421,7 +422,7 @@ class Server implements ResourceControllerInterface, * @param string $scope - Scope * @return mixed */ - public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response = null, $scope = null) + public function verifyResourceRequest(RequestInterface $request, ?ResponseInterface $response = null, $scope = null) { $this->response = is_null($response) ? new Response() : $response; $value = $this->getResourceController()->verifyResourceRequest($request, $this->response, $scope); @@ -434,7 +435,7 @@ class Server implements ResourceControllerInterface, * @param ResponseInterface $response - Response object * @return mixed */ - public function getAccessTokenData(RequestInterface $request, ResponseInterface $response = null) + public function getAccessTokenData(RequestInterface $request, ?ResponseInterface $response = null) { $this->response = is_null($response) ? new Response() : $response; $value = $this->getResourceController()->getAccessTokenData($request, $this->response); @@ -577,7 +578,7 @@ class Server implements ResourceControllerInterface, } } - $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri'))); + $config = array_intersect_key($this->config, array_flip(explode(' ', 'allow_implicit enforce_state require_exact_redirect_uri enforce_pkce'))); if ($this->config['use_openid_connect']) { return new OpenIDAuthorizeController($this->storages['client'], $this->responseTypes, $config, $this->getScopeUtil()); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php index e60e9d3ad..3a138bb52 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Cassandra.php @@ -191,11 +191,11 @@ class Cassandra implements AuthorizationCodeInterface, * @param string $id_token * @return bool */ - public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { return $this->setValue( $this->config['code_key'] . $authorization_code, - compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'), + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token', 'code_challenge', 'code_challenge_method'), $expires ); } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php index 9e8148b6b..31b0cd301 100755 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/CouchbaseDB.php @@ -2,6 +2,7 @@ namespace OAuth2\Storage; +use Couchbase; use OAuth2\OpenID\Storage\AuthorizationCodeInterface as OpenIDAuthorizationCodeInterface; /** @@ -27,14 +28,18 @@ class CouchbaseDB implements AuthorizationCodeInterface, public function __construct($connection, $config = array()) { - if ($connection instanceof \Couchbase) { + if (!class_exists(Couchbase::class)) { + throw new \RuntimeException('Missing Couchbase'); + } + + if ($connection instanceof Couchbase) { $this->db = $connection; } else { if (!is_array($connection) || !is_array($connection['servers'])) { throw new \InvalidArgumentException('First argument to OAuth2\Storage\CouchbaseDB must be an instance of Couchbase or a configuration array containing a server array'); } - $this->db = new \Couchbase($connection['servers'], (!isset($connection['username'])) ? '' : $connection['username'], (!isset($connection['password'])) ? '' : $connection['password'], $connection['bucket'], false); + $this->db = new Couchbase($connection['servers'], (!isset($connection['username'])) ? '' : $connection['username'], (!isset($connection['password'])) ? '' : $connection['password'], $connection['bucket'], false); } $this->config = array_merge(array( @@ -173,7 +178,7 @@ class CouchbaseDB implements AuthorizationCodeInterface, return is_null($code) ? false : $code; } - public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { // if it exists, update it. if ($this->getAuthorizationCode($code)) { @@ -185,6 +190,8 @@ class CouchbaseDB implements AuthorizationCodeInterface, 'expires' => $expires, 'scope' => $scope, 'id_token' => $id_token, + 'code_challenge' => $code_challenge, + 'code_challenge_method' => $code_challenge_method, )); } else { $this->setObjectByType('code_table',$code,array( @@ -195,6 +202,8 @@ class CouchbaseDB implements AuthorizationCodeInterface, 'expires' => $expires, 'scope' => $scope, 'id_token' => $id_token, + 'code_challenge' => $code_challenge, + 'code_challenge_method' => $code_challenge_method, )); } @@ -328,4 +337,4 @@ class CouchbaseDB implements AuthorizationCodeInterface, //TODO: Needs couchbase implementation. throw new \Exception('setJti() for the Couchbase driver is currently unimplemented.'); } -} \ No newline at end of file +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php index a54cb3712..713189d23 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/DynamoDB.php @@ -213,12 +213,12 @@ class DynamoDB implements } - public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { // convert expires to datestring $expires = date('Y-m-d H:i:s', $expires); - $clientData = compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'id_token', 'scope'); + $clientData = compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token', 'code_challenge', 'code_challenge_method'); $clientData = array_filter($clientData, 'self::isNotEmpty'); $result = $this->client->putItem(array( diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php index 6ccacd6d9..73c5a3e62 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/JwtAccessToken.php @@ -21,7 +21,7 @@ class JwtAccessToken implements JwtAccessTokenInterface * is not necessary when using this grant type. * @param OAuth2\Encryption\EncryptionInterface $encryptionUtil OPTIONAL class to use for "encode" and "decode" functions. */ - public function __construct(PublicKeyInterface $publicKeyStorage, AccessTokenInterface $tokenStorage = null, EncryptionInterface $encryptionUtil = null) + public function __construct(PublicKeyInterface $publicKeyStorage, ?AccessTokenInterface $tokenStorage = null, ?EncryptionInterface $encryptionUtil = null) { $this->publicKeyStorage = $publicKeyStorage; $this->tokenStorage = $tokenStorage; @@ -84,4 +84,4 @@ class JwtAccessToken implements JwtAccessTokenInterface return $tokenData; } -} \ No newline at end of file +} diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php index 2c60b71ce..c33bd0ebb 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Memory.php @@ -74,9 +74,9 @@ class Memory implements AuthorizationCodeInterface, ), $this->authorizationCodes[$code]); } - public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { - $this->authorizationCodes[$code] = compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'); + $this->authorizationCodes[$code] = compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token', 'code_challenge', 'code_challenge_method'); return true; } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php index eea06e315..92f93d5b2 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Mongo.php @@ -179,7 +179,7 @@ class Mongo implements AuthorizationCodeInterface, return is_null($code) ? false : $code; } - public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { // if it exists, update it. if ($this->getAuthorizationCode($code)) { @@ -192,6 +192,8 @@ class Mongo implements AuthorizationCodeInterface, 'expires' => $expires, 'scope' => $scope, 'id_token' => $id_token, + 'code_challenge' => $code_challenge, + 'code_challenge_method' => $code_challenge_method, )) ); } else { @@ -203,6 +205,8 @@ class Mongo implements AuthorizationCodeInterface, 'expires' => $expires, 'scope' => $scope, 'id_token' => $id_token, + 'code_challenge' => $code_challenge, + 'code_challenge_method' => $code_challenge_method, ); $this->collection('code_table')->insert($token); } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php index 64f740fc1..0b28a7797 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/MongoDB.php @@ -32,6 +32,9 @@ class MongoDB implements AuthorizationCodeInterface, public function __construct($connection, $config = array()) { + if (!class_exists(Database::class) || !class_exists(Client::class)) { + throw new \LogicException('Missing MongoDB php extension. Please install mongodb.so'); + } if ($connection instanceof Database) { $this->db = $connection; } else { @@ -167,7 +170,7 @@ class MongoDB implements AuthorizationCodeInterface, return is_null($code) ? false : $code; } - public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { // if it exists, update it. if ($this->getAuthorizationCode($code)) { @@ -180,6 +183,8 @@ class MongoDB implements AuthorizationCodeInterface, 'expires' => $expires, 'scope' => $scope, 'id_token' => $id_token, + 'code_challenge' => $code_challenge, + 'code_challenge_method' => $code_challenge_method, )) ); return $result->getMatchedCount() > 0; @@ -192,6 +197,8 @@ class MongoDB implements AuthorizationCodeInterface, 'expires' => $expires, 'scope' => $scope, 'id_token' => $id_token, + 'code_challenge' => $code_challenge, + 'code_challenge_method' => $code_challenge_method, ); $result = $this->collection('code_table')->insertOne($token); return $result->getInsertedCount() > 0; diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php index 074cee447..46c873359 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php @@ -247,7 +247,7 @@ class Pdo implements * @param string $id_token * @return bool|mixed */ - public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { if (func_num_args() > 6) { // we are calling with an id token @@ -259,12 +259,12 @@ class Pdo implements // if it exists, update it. if ($this->getAuthorizationCode($code)) { - $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope where authorization_code=:code', $this->config['code_table'])); + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, code_challenge=:code_challenge, code_challenge_method=:code_challenge_method where authorization_code=:code', $this->config['code_table'])); } else { - $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table'])); + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, code_challenge, code_challenge_method) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :code_challenge, :code_challenge_method)', $this->config['code_table'])); } - return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope')); + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'code_challenge', 'code_challenge_method')); } /** @@ -277,19 +277,19 @@ class Pdo implements * @param string $id_token * @return bool */ - private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { // convert expires to datestring $expires = date('Y-m-d H:i:s', $expires); // if it exists, update it. if ($this->getAuthorizationCode($code)) { - $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token where authorization_code=:code', $this->config['code_table'])); + $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token, code_challenge=:code_challenge, code_challenge_method=:code_challenge_method where authorization_code=:code', $this->config['code_table'])); } else { - $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token)', $this->config['code_table'])); + $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token, code_challenge, code_challenge_method) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token, :code_challenge, :code_challenge_method)', $this->config['code_table'])); } - return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token')); + return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token', 'code_challenge', 'code_challenge_method')); } /** @@ -676,6 +676,8 @@ class Pdo implements expires TIMESTAMP NOT NULL, scope VARCHAR(4000), id_token VARCHAR(1000), + code_challenge VARCHAR(1000), + code_challenge_method VARCHAR(20), PRIMARY KEY (authorization_code) ); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php index e6294e22d..5a41dfc22 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Redis.php @@ -95,11 +95,11 @@ class Redis implements AuthorizationCodeInterface, return $this->getValue($this->config['code_key'] . $code); } - public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + public function setAuthorizationCode($authorization_code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null) { return $this->setValue( $this->config['code_key'] . $authorization_code, - compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token'), + compact('authorization_code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token', 'code_challenge', 'code_challenge_method'), $expires ); } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php index 8ac8596ac..aa1094b1e 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/src/OAuth2/TokenType/Bearer.php @@ -109,7 +109,7 @@ class Bearer implements TokenTypeInterface } $contentType = $request->server('CONTENT_TYPE'); - if (false !== $pos = strpos($contentType, ';')) { + if (false !== $pos = strpos((string) $contentType, ';')) { $contentType = substr($contentType, 0, $pos); } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php index fe3553b23..88f0d0da0 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Controller/AuthorizeControllerTest.php @@ -133,7 +133,7 @@ class AuthorizeControllerTest extends TestCase $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertNotContains('error', $response->getHttpHeader('Location')); + $this->assertStringNotContainsString('error', $response->getHttpHeader('Location')); } public function testEnforceScope() @@ -161,7 +161,7 @@ class AuthorizeControllerTest extends TestCase $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertNotContains('error', $response->getHttpHeader('Location')); + $this->assertStringNotContainsString('error', $response->getHttpHeader('Location')); } public function testInvalidRedirectUri() @@ -227,7 +227,7 @@ class AuthorizeControllerTest extends TestCase $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertContains('code', $response->getHttpHeader('Location')); + $this->assertStringContainsString('code', $response->getHttpHeader('Location')); } public function testRedirectUriWithDifferentQueryAndExactMatchRequired() @@ -263,7 +263,7 @@ class AuthorizeControllerTest extends TestCase $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertContains('code', $response->getHttpHeader('Location')); + $this->assertStringContainsString('code', $response->getHttpHeader('Location')); } public function testMultipleRedirectUris() @@ -278,14 +278,14 @@ class AuthorizeControllerTest extends TestCase $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertContains('code', $response->getHttpHeader('Location')); + $this->assertStringContainsString('code', $response->getHttpHeader('Location')); // call again with different (but still valid) redirect URI $request->query['redirect_uri'] = 'http://morehazards.com'; $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertContains('code', $response->getHttpHeader('Location')); + $this->assertStringContainsString('code', $response->getHttpHeader('Location')); } /** @@ -303,7 +303,7 @@ class AuthorizeControllerTest extends TestCase $server->handleAuthorizeRequest($request, $response = new Response(), true); $this->assertEquals($response->getStatusCode(), 302); - $this->assertContains('state', $response->getHttpHeader('Location')); + $this->assertStringContainsString('state', $response->getHttpHeader('Location')); $this->assertStringStartsWith('http://brentertainment.com?code=', $response->getHttpHeader('Location')); $parts = parse_url($response->getHttpHeader('Location')); @@ -421,7 +421,7 @@ class AuthorizeControllerTest extends TestCase $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertFalse(isset($parts['fake'])); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php index c7e92c053..63a5d4036 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Encryption/FirebaseJwtTest.php @@ -9,7 +9,7 @@ class FirebaseJwtTest extends TestCase { private $privateKey; - public function setUp() + public function setUp(): void { $this->privateKey = <<privateKey = <<privateKey = <<assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertArrayHasKey('query', $parts); @@ -87,7 +87,7 @@ class CodeIdTokenTest extends TestCase $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertArrayHasKey('query', $parts); @@ -133,7 +133,7 @@ class CodeIdTokenTest extends TestCase $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertArrayHasKey('query', $parts); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php index a0df3a936..85f8abc86 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTest.php @@ -52,7 +52,7 @@ class IdTokenTest extends TestCase $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertArrayHasKey('fragment', $parts); @@ -110,7 +110,7 @@ class IdTokenTest extends TestCase { $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertArrayHasKey('fragment', $parts); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php index 0573a9866..6d4cb14c3 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/OpenID/ResponseType/IdTokenTokenTest.php @@ -31,7 +31,7 @@ class IdTokenTokenTest extends TestCase $this->assertEquals($response->getStatusCode(), 302); $location = $response->getHttpHeader('Location'); - $this->assertNotContains('error', $location); + $this->assertStringNotContainsString('error', $location); $parts = parse_url($location); $this->assertArrayHasKey('fragment', $parts); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php index 172bc88fd..338f5cc93 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseTest.php @@ -14,7 +14,7 @@ class ResponseTest extends TestCase )); $string = $response->getResponseBody('xml'); - $this->assertContains('baroates', $string); + $this->assertStringContainsString('baroates', $string); } public function testSetRedirect() diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php index 6195d557a..ad90e535b 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ResponseType/JwtAccessTokenTest.php @@ -40,20 +40,20 @@ class JwtAccessTokenTest extends TestCase $this->assertEquals(3600, $delta); $this->assertEquals($decodedAccessToken['id'], $decodedAccessToken['jti']); } - + public function testExtraPayloadCallback() { $jwtconfig = array('jwt_extra_payload_callable' => function() { return array('custom_param' => 'custom_value'); }); - + $server = $this->getTestServer($jwtconfig); $jwtResponseType = $server->getResponseType('token'); - + $accessToken = $jwtResponseType->createAccessToken('Test Client ID', 123, 'test', false); $jwt = new Jwt; $decodedAccessToken = $jwt->decode($accessToken['access_token'], null, false); - + $this->assertArrayHasKey('custom_param', $decodedAccessToken); $this->assertEquals('custom_value', $decodedAccessToken['custom_param']); } @@ -162,7 +162,7 @@ class JwtAccessTokenTest extends TestCase $memoryStorage = Bootstrap::getInstance()->getMemoryStorage(); $storage = array( - 'access_token' => new JwtAccessTokenStorage($memoryStorage), + 'access_token' => new JwtAccessTokenStorage($memoryStorage, $memoryStorage), 'client' => $memoryStorage, 'client_credentials' => $memoryStorage, ); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php index 3106961e2..fab526a6f 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/ServerTest.php @@ -6,27 +6,26 @@ use OAuth2\Request\TestRequest; use OAuth2\ResponseType\AuthorizationCode; use OAuth2\Storage\Bootstrap; use PHPUnit\Framework\TestCase; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectPHPException; class ServerTest extends TestCase { - /** - * @expectedException LogicException OAuth2\Storage\ClientInterface - **/ + use ExpectPHPException; + public function testGetAuthorizeControllerWithNoClientStorageThrowsException() { + $this->expectExceptionMessage('OAuth2\Storage\ClientInterface'); // must set Client Storage $server = new Server(); $server->getAuthorizeController(); } - /** - * @expectedException LogicException OAuth2\Storage\AccessTokenInterface - **/ public function testGetAuthorizeControllerWithNoAccessTokenStorageThrowsException() { + $this->expectExceptionMessage('OAuth2\Storage\AccessTokenInterface'); // must set AccessToken or AuthorizationCode $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientInterface')); $server->getAuthorizeController(); } @@ -34,8 +33,8 @@ class ServerTest extends TestCase { // must set AccessToken or AuthorizationCode $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); - $server->addResponseType($this->getMock('OAuth2\ResponseType\AccessTokenInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientInterface')); + $server->addResponseType($this->createMock('OAuth2\ResponseType\AccessTokenInterface')); $this->assertNotNull($server->getAuthorizeController()); } @@ -44,21 +43,19 @@ class ServerTest extends TestCase { // must set AccessToken or AuthorizationCode $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); - $server->addResponseType($this->getMock('OAuth2\ResponseType\AuthorizationCodeInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientInterface')); + $server->addResponseType($this->createMock('OAuth2\ResponseType\AuthorizationCodeInterface')); $this->assertNotNull($server->getAuthorizeController()); } - /** - * @expectedException LogicException allow_implicit - **/ public function testGetAuthorizeControllerWithClientStorageAndAccessTokenStorageThrowsException() { // must set AuthorizationCode or AccessToken / implicit + $this->expectExceptionMessage('allow_implicit'); $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface')); $this->assertNotNull($server->getAuthorizeController()); } @@ -67,8 +64,8 @@ class ServerTest extends TestCase { // must set AuthorizationCode or AccessToken / implicit $server = new Server(array(), array('allow_implicit' => true)); - $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface')); $this->assertNotNull($server->getAuthorizeController()); } @@ -77,63 +74,55 @@ class ServerTest extends TestCase { // must set AccessToken or AuthorizationCode $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\ClientInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\AuthorizationCodeInterface')); $this->assertNotNull($server->getAuthorizeController()); } - /** - * @expectedException LogicException grant_types - **/ public function testGetTokenControllerWithGrantTypeStorageThrowsException() { + $this->expectExceptionMessage('grant_types'); $server = new Server(); $server->getTokenController(); } - /** - * @expectedException LogicException OAuth2\Storage\ClientCredentialsInterface - **/ public function testGetTokenControllerWithNoClientCredentialsStorageThrowsException() { + $this->expectExceptionMessage('OAuth2\Storage\ClientCredentialsInterface'); $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\UserCredentialsInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\UserCredentialsInterface')); $server->getTokenController(); } - /** - * @expectedException LogicException OAuth2\Storage\AccessTokenInterface - **/ public function testGetTokenControllerWithNoAccessTokenStorageThrowsException() { + $this->expectExceptionMessage('OAuth2\Storage\AccessTokenInterface'); $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientCredentialsInterface')); $server->getTokenController(); } public function testGetTokenControllerWithAccessTokenAndClientCredentialsStorage() { $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientCredentialsInterface')); $server->getTokenController(); } public function testGetTokenControllerAccessTokenStorageAndClientCredentialsStorageAndGrantTypes() { $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); - $server->addGrantType($this->getMockBuilder('OAuth2\GrantType\AuthorizationCode')->disableOriginalConstructor()->getMock()); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addGrantType($this->createMock('OAuth2\GrantType\AuthorizationCode')); $server->getTokenController(); } - /** - * @expectedException LogicException OAuth2\Storage\AccessTokenInterface - **/ public function testGetResourceControllerWithNoAccessTokenStorageThrowsException() { + $this->expectExceptionMessage('OAuth2\Storage\AccessTokenInterface'); $server = new Server(); $server->getResourceController(); } @@ -141,41 +130,35 @@ class ServerTest extends TestCase public function testGetResourceControllerWithAccessTokenStorage() { $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface')); $server->getResourceController(); } - /** - * @expectedException InvalidArgumentException OAuth2\Storage\AccessTokenInterface - **/ public function testAddingStorageWithInvalidClass() { + $this->expectExceptionMessage('OAuth2\Storage\AccessTokenInterface'); $server = new Server(); $server->addStorage(new \StdClass()); } - /** - * @expectedException InvalidArgumentException access_token - **/ public function testAddingStorageWithInvalidKey() { + $this->expectExceptionMessage('access_token'); $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface'), 'nonexistant_storage'); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface'), 'nonexistant_storage'); } - /** - * @expectedException InvalidArgumentException OAuth2\Storage\AuthorizationCodeInterface - **/ public function testAddingStorageWithInvalidKeyStorageCombination() { + $this->expectExceptionMessage('OAuth2\Storage\AuthorizationCodeInterface'); $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\AccessTokenInterface'), 'authorization_code'); + $server->addStorage($this->createMock('OAuth2\Storage\AccessTokenInterface'), 'authorization_code'); } public function testAddingStorageWithValidKeyOnlySetsThatKey() { $server = new Server(); - $server->addStorage($this->getMock('OAuth2\Storage\Memory'), 'access_token'); + $server->addStorage($this->createMock('OAuth2\Storage\Memory'), 'access_token'); $reflection = new \ReflectionClass($server); $prop = $reflection->getProperty('storages'); @@ -191,7 +174,7 @@ class ServerTest extends TestCase public function testAddingClientStorageSetsClientCredentialsStorageByDefault() { $server = new Server(); - $memory = $this->getMock('OAuth2\Storage\Memory'); + $memory = $this->createMock('OAuth2\Storage\Memory'); $server->addStorage($memory, 'client'); $client_credentials = $server->getStorage('client_credentials'); @@ -202,7 +185,7 @@ class ServerTest extends TestCase public function testAddStorageWithNullValue() { - $memory = $this->getMock('OAuth2\Storage\Memory'); + $memory = $this->createMock('OAuth2\Storage\Memory'); $server = new Server($memory); $server->addStorage(null, 'refresh_token'); @@ -218,7 +201,7 @@ class ServerTest extends TestCase public function testNewServerWithNullStorageValue() { - $memory = $this->getMock('OAuth2\Storage\Memory'); + $memory = $this->createMock('OAuth2\Storage\Memory'); $server = new Server(array( 'client_credentials' => $memory, 'refresh_token' => null, @@ -237,7 +220,7 @@ class ServerTest extends TestCase public function testAddingClientCredentialsStorageSetsClientStorageByDefault() { $server = new Server(); - $memory = $this->getMock('OAuth2\Storage\Memory'); + $memory = $this->createMock('OAuth2\Storage\Memory'); $server->addStorage($memory, 'client_credentials'); $client = $server->getStorage('client'); @@ -249,10 +232,9 @@ class ServerTest extends TestCase public function testSettingClientStorageByDefaultDoesNotOverrideSetStorage() { $server = new Server(); - $pdo = $this->getMockBuilder('OAuth2\Storage\Pdo') - ->disableOriginalConstructor()->getMock(); + $pdo = $this->createMock('OAuth2\Storage\Pdo'); - $memory = $this->getMock('OAuth2\Storage\Memory'); + $memory = $this->createMock('OAuth2\Storage\Memory'); $server->addStorage($pdo, 'client'); $server->addStorage($memory, 'client_credentials'); @@ -266,7 +248,7 @@ class ServerTest extends TestCase public function testAddingResponseType() { - $storage = $this->getMock('OAuth2\Storage\Memory'); + $storage = $this->createMock('OAuth2\Storage\Memory'); $storage ->expects($this->any()) ->method('getClientDetails') @@ -323,7 +305,7 @@ class ServerTest extends TestCase 'code' => 'testcode', )); // verify the mock clientAssertionType was called as expected - $clientAssertionType = $this->getMock('OAuth2\ClientAssertionType\ClientAssertionTypeInterface', array('validateRequest', 'getClientId')); + $clientAssertionType = $this->createMock('OAuth2\ClientAssertionType\ClientAssertionTypeInterface'); $clientAssertionType ->expects($this->once()) ->method('validateRequest') @@ -420,98 +402,84 @@ class ServerTest extends TestCase $this->assertFalse($used_token, 'the refresh token used is no longer valid'); } - /** - * @expectedException InvalidArgumentException OAuth2\ResponseType\AuthorizationCodeInterface - **/ public function testAddingUnknownResponseTypeThrowsException() { + $this->expectExceptionMessage('OAuth2\ResponseType\AuthorizationCodeInterface'); $server = new Server(); - $server->addResponseType($this->getMock('OAuth2\ResponseType\ResponseTypeInterface')); + $server->addResponseType($this->createMock('OAuth2\ResponseType\ResponseTypeInterface')); } - /** - * @expectedException LogicException OAuth2\Storage\PublicKeyInterface - **/ public function testUsingJwtAccessTokensWithoutPublicKeyStorageThrowsException() { + $this->expectExceptionMessage('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array(), array('use_jwt_access_tokens' => true)); - $server->addGrantType($this->getMock('OAuth2\GrantType\GrantTypeInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); - $server->addStorage($this->getMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addGrantType($this->createMock('OAuth2\GrantType\GrantTypeInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientCredentialsInterface')); + $server->addStorage($this->createMock('OAuth2\Storage\ClientCredentialsInterface')); $server->getTokenController(); } public function testUsingJustJwtAccessTokenStorageWithResourceControllerIsOkay() { - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); $this->assertNotNull($server->getResourceController()); $this->assertInstanceOf('OAuth2\Storage\PublicKeyInterface', $server->getStorage('public_key')); } - /** - * @expectedException LogicException OAuth2\Storage\ClientInterface - **/ public function testUsingJustJwtAccessTokenStorageWithAuthorizeControllerThrowsException() { - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $this->expectExceptionMessage('OAuth2\Storage\ClientInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); $this->assertNotNull($server->getAuthorizeController()); } - /** - * @expectedException LogicException grant_types - **/ public function testUsingJustJwtAccessTokenStorageWithTokenControllerThrowsException() { - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $this->expectExceptionMessage('grant_types'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($pubkey), array('use_jwt_access_tokens' => true)); $server->getTokenController(); } public function testUsingJwtAccessTokenAndClientStorageWithAuthorizeControllerIsOkay() { - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); - $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); $server = new Server(array($pubkey, $client), array('use_jwt_access_tokens' => true, 'allow_implicit' => true)); $this->assertNotNull($server->getAuthorizeController()); $this->assertInstanceOf('OAuth2\ResponseType\JwtAccessToken', $server->getResponseType('token')); } - /** - * @expectedException LogicException UserClaims - **/ public function testUsingOpenIDConnectWithoutUserClaimsThrowsException() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); + $this->expectExceptionMessage('UserClaims'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); $server = new Server($client, array('use_openid_connect' => true)); $server->getAuthorizeController(); } - /** - * @expectedException LogicException PublicKeyInterface - **/ public function testUsingOpenIDConnectWithoutPublicKeyThrowsException() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OPenID\Storage\UserClaimsInterface'); + $this->expectExceptionMessage('PublicKeyInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OPenID\Storage\UserClaimsInterface'); $server = new Server(array($client, $userclaims), array('use_openid_connect' => true)); $server->getAuthorizeController(); } - /** - * @expectedException LogicException issuer - **/ public function testUsingOpenIDConnectWithoutIssuerThrowsException() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $this->expectExceptionMessage('issuer'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($client, $userclaims, $pubkey), array('use_openid_connect' => true)); $server->getAuthorizeController(); @@ -519,9 +487,9 @@ class ServerTest extends TestCase public function testUsingOpenIDConnectWithIssuerPublicKeyAndUserClaimsIsOkay() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($client, $userclaims, $pubkey), array( 'use_openid_connect' => true, 'issuer' => 'someguy', @@ -533,14 +501,12 @@ class ServerTest extends TestCase $this->assertNull($server->getResponseType('id_token token')); } - /** - * @expectedException LogicException OAuth2\ResponseType\AccessTokenInterface - **/ public function testUsingOpenIDConnectWithAllowImplicitWithoutTokenStorageThrowsException() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $this->expectErrorMessage('OAuth2\ResponseType\AccessTokenInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($client, $userclaims, $pubkey), array( 'use_openid_connect' => true, 'issuer' => 'someguy', @@ -552,9 +518,9 @@ class ServerTest extends TestCase public function testUsingOpenIDConnectWithAllowImplicitAndUseJwtAccessTokensIsOkay() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); $server = new Server(array($client, $userclaims, $pubkey), array( 'use_openid_connect' => true, 'issuer' => 'someguy', @@ -570,10 +536,10 @@ class ServerTest extends TestCase public function testUsingOpenIDConnectWithAllowImplicitAndAccessTokenStorageIsOkay() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); - $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->createMock('OAuth2\Storage\AccessTokenInterface'); $server = new Server(array($client, $userclaims, $pubkey, $token), array( 'use_openid_connect' => true, 'issuer' => 'someguy', @@ -588,17 +554,17 @@ class ServerTest extends TestCase public function testUsingOpenIDConnectWithAllowImplicitAndAccessTokenResponseTypeIsOkay() { - $client = $this->getMock('OAuth2\Storage\ClientInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); - // $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); + $client = $this->createMock('OAuth2\Storage\ClientInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); + // $token = $this->createMock('OAuth2\Storage\AccessTokenInterface'); $server = new Server(array($client, $userclaims, $pubkey), array( 'use_openid_connect' => true, 'issuer' => 'someguy', 'allow_implicit' => true, )); - $token = $this->getMock('OAuth2\ResponseType\AccessTokenInterface'); + $token = $this->createMock('OAuth2\ResponseType\AccessTokenInterface'); $server->addResponseType($token, 'token'); $server->getAuthorizeController(); @@ -607,17 +573,15 @@ class ServerTest extends TestCase $this->assertInstanceOf('OAuth2\OpenID\ResponseType\IdTokenTokenInterface', $server->getResponseType('id_token token')); } - /** - * @expectedException LogicException OAuth2\OpenID\Storage\AuthorizationCodeInterface - **/ public function testUsingOpenIDConnectWithAuthorizationCodeStorageThrowsException() { - $client = $this->getMock('OAuth2\Storage\ClientCredentialsInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); - $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); - $authcode = $this->getMock('OAuth2\Storage\AuthorizationCodeInterface'); + $client = $this->createMock('OAuth2\Storage\ClientCredentialsInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->createMock('OAuth2\Storage\AccessTokenInterface'); + $authcode = $this->createMock('OAuth2\Storage\AuthorizationCodeInterface'); + $this->expectErrorMessage('OAuth2\OpenID\Storage\AuthorizationCodeInterface'); $server = new Server(array($client, $userclaims, $pubkey, $token, $authcode), array( 'use_openid_connect' => true, 'issuer' => 'someguy' @@ -630,11 +594,11 @@ class ServerTest extends TestCase public function testUsingOpenIDConnectWithOpenIDAuthorizationCodeStorageCreatesOpenIDAuthorizationCodeGrantType() { - $client = $this->getMock('OAuth2\Storage\ClientCredentialsInterface'); - $userclaims = $this->getMock('OAuth2\OpenID\Storage\UserClaimsInterface'); - $pubkey = $this->getMock('OAuth2\Storage\PublicKeyInterface'); - $token = $this->getMock('OAuth2\Storage\AccessTokenInterface'); - $authcode = $this->getMock('OAuth2\OpenID\Storage\AuthorizationCodeInterface'); + $client = $this->createMock('OAuth2\Storage\ClientCredentialsInterface'); + $userclaims = $this->createMock('OAuth2\OpenID\Storage\UserClaimsInterface'); + $pubkey = $this->createMock('OAuth2\Storage\PublicKeyInterface'); + $token = $this->createMock('OAuth2\Storage\AccessTokenInterface'); + $authcode = $this->createMock('OAuth2\OpenID\Storage\AuthorizationCodeInterface'); $server = new Server(array($client, $userclaims, $pubkey, $token, $authcode), array( 'use_openid_connect' => true, @@ -648,7 +612,7 @@ class ServerTest extends TestCase public function testMultipleValuedResponseTypeOrderDoesntMatter() { - $responseType = $this->getMock('OAuth2\OpenID\ResponseType\IdTokenTokenInterface'); + $responseType = $this->createMock('OAuth2\OpenID\ResponseType\IdTokenTokenInterface'); $server = new Server(array(), array(), array(), array( 'token id_token' => $responseType, )); @@ -659,7 +623,7 @@ class ServerTest extends TestCase public function testAddGrantTypeWithoutKey() { $server = new Server(); - $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface'))); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->createMock('OAuth2\Storage\AuthorizationCodeInterface'))); $grantTypes = $server->getGrantTypes(); $this->assertEquals('authorization_code', key($grantTypes)); @@ -668,7 +632,7 @@ class ServerTest extends TestCase public function testAddGrantTypeWithKey() { $server = new Server(); - $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')), 'ac'); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->createMock('OAuth2\Storage\AuthorizationCodeInterface')), 'ac'); $grantTypes = $server->getGrantTypes(); $this->assertEquals('ac', key($grantTypes)); @@ -677,7 +641,7 @@ class ServerTest extends TestCase public function testAddGrantTypeWithKeyNotString() { $server = new Server(); - $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->getMock('OAuth2\Storage\AuthorizationCodeInterface')), 42); + $server->addGrantType(new \OAuth2\GrantType\AuthorizationCode($this->createMock('OAuth2\Storage\AuthorizationCodeInterface')), 42); $grantTypes = $server->getGrantTypes(); $this->assertEquals('authorization_code', key($grantTypes)); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php index 4599f69bf..9a7630423 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/Storage/PdoTest.php @@ -2,8 +2,12 @@ namespace OAuth2\Storage; +use Yoast\PHPUnitPolyfills\Polyfills\ExpectPHPException; + class PdoTest extends BaseTest { + use ExpectPHPException; + public function testCreatePdoStorageUsingPdoClass() { $dsn = sprintf('sqlite:%s', Bootstrap::getInstance()->getSqliteDir()); @@ -30,11 +34,9 @@ class PdoTest extends BaseTest $this->assertNotNull($storage->getClientDetails('oauth_test_client')); } - /** - * @expectedException InvalidArgumentException dsn - */ public function testCreatePdoStorageWithoutDSNThrowsException() { + $this->expectErrorMessage('dsn'); $config = array('username' => 'brent', 'password' => 'brentisaballer'); $storage = new Pdo($config); } diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php index 71cca3bd9..021fc7f5a 100644 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/OAuth2/TokenType/BearerTest.php @@ -35,6 +35,17 @@ class BearerTest extends TestCase $this->assertEquals($response->getParameter('error_description'), 'The content type for POST requests must be "application/x-www-form-urlencoded"'); } + public function testMissingContentTypeExpectsToBeCorrectContent() + { + $bearer = new Bearer(); + $request = TestRequest::createPost(array( + 'access_token' => 'ThisIsMyAccessToken' + )); + + $param = $bearer->getAccessTokenParameter($request, $response = new Response()); + $this->assertEquals($param, 'ThisIsMyAccessToken'); + } + public function testValidRequestUsingAuthorizationHeader() { $bearer = new Bearer(); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/cleanup.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/cleanup.php deleted file mode 100644 index 8663a901b..000000000 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/cleanup.php +++ /dev/null @@ -1,15 +0,0 @@ -cleanupTravisDynamoDb(); diff --git a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php index 8e428f9b5..66c93ae5b 100755 --- a/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php +++ b/data/web/inc/lib/vendor/bshaffer/oauth2-server-php/test/lib/OAuth2/Storage/Bootstrap.php @@ -68,7 +68,7 @@ class Bootstrap public function getPostgresDriver() { try { - $pdo = new \PDO('pgsql:host=localhost;dbname=oauth2_server_php', 'postgres'); + $pdo = new \PDO('pgsql:host=localhost;dbname=oauth2_server_php', 'postgres', 'postgres'); return $pdo; } catch (\PDOException $e) { @@ -118,7 +118,7 @@ class Bootstrap if (!$this->mysql) { $pdo = null; try { - $pdo = new \PDO('mysql:host=localhost;', 'root'); + $pdo = new \PDO('mysql:host=localhost;', 'root', 'root'); } catch (\PDOException $e) { $this->mysql = new NullStorage('MySQL', 'Unable to connect to MySQL on root@localhost'); } @@ -352,11 +352,11 @@ class Bootstrap private function createPostgresDb() { - if (!`psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='postgres'"`) { - `createuser -s -r postgres`; + if (!`PGPASSWORD=postgres psql postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='postgres'" -h localhost -U postgres`) { + `PGPASSWORD=postgres createuser -s -r postgres -h localhost -U postgres`; } - `createdb -O postgres oauth2_server_php`; + `PGPASSWORD=postgres createdb -O postgres oauth2_server_php -h localhost -U postgres`; } private function populatePostgresDb(\PDO $pdo) @@ -366,8 +366,8 @@ class Bootstrap private function removePostgresDb() { - if (trim(`psql -l | grep oauth2_server_php | wc -l`)) { - `dropdb oauth2_server_php`; + if (trim(`PGPASSWORD=postgres psql -l -h localhost -U postgres | grep oauth2_server_php | wc -l`)) { + `PGPASSWORD=postgres dropdb oauth2_server_php -h localhost -U postgres`; } } @@ -945,21 +945,6 @@ class Bootstrap )); } - public function cleanupTravisDynamoDb($prefix = null) - { - if (is_null($prefix)) { - // skip this when not applicable - if (!$this->getEnvVar('TRAVIS') || self::DYNAMODB_PHP_VERSION != $this->getEnvVar('TRAVIS_PHP_VERSION')) { - return; - } - - $prefix = sprintf('build_%s_', $this->getEnvVar('TRAVIS_JOB_NUMBER')); - } - - $client = $this->getDynamoDbClient(); - $this->deleteDynamoDb($client, $prefix); - } - private function getEnvVar($var, $default = null) { return isset($_SERVER[$var]) ? $_SERVER[$var] : (getenv($var) ?: $default); diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md index 5a18121b6..baaf5fea2 100644 --- a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/README.md @@ -4,7 +4,7 @@ Types to use Carbon in Doctrine ## Documentation -[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/) +[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/guide/getting-started/symfony.html) This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine) from `nestbot/carbon` package. diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json index abf45c5fb..dbc90ec1e 100644 --- a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/composer.json @@ -32,5 +32,10 @@ "email": "kylekatarnls@gmail.com" } ], - "minimum-stability": "dev" + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + } } diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php index a63a9b8d2..8cb82202a 100644 --- a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php @@ -4,13 +4,14 @@ declare(strict_types=1); namespace Carbon\Doctrine; +use Carbon\CarbonInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; interface CarbonDoctrineType { - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform); + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string; - public function convertToPHPValue(mixed $value, AbstractPlatform $platform); + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonInterface; - public function convertToDatabaseValue($value, AbstractPlatform $platform); + public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string; } diff --git a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonTypeConverter.php b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonTypeConverter.php index a81331177..d2e3786c4 100644 --- a/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonTypeConverter.php +++ b/data/web/inc/lib/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonTypeConverter.php @@ -62,7 +62,7 @@ trait CarbonTypeConverter /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string { if ($value === null) { return $value; @@ -79,11 +79,11 @@ trait CarbonTypeConverter ); } - private function doConvertToPHPValue(mixed $value) + private function doConvertToPHPValue(mixed $value): ?CarbonInterface { $class = $this->getCarbonClassName(); - if ($value === null || is_a($value, $class)) { + if ($value === null || (is_object($value) && is_a($value, $class))) { return $value; } diff --git a/data/web/inc/lib/vendor/composer/InstalledVersions.php b/data/web/inc/lib/vendor/composer/InstalledVersions.php index 6d29bff66..2052022fd 100644 --- a/data/web/inc/lib/vendor/composer/InstalledVersions.php +++ b/data/web/inc/lib/vendor/composer/InstalledVersions.php @@ -26,6 +26,12 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null @@ -322,6 +328,18 @@ class InstalledVersions self::$installedIsLocalDir = false; } + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + /** * @return array[] * @psalm-return list}> @@ -336,7 +354,7 @@ class InstalledVersions $copiedLocalDir = false; if (self::$canGetVendors) { - $selfDir = strtr(__DIR__, '\\', '/'); + $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { diff --git a/data/web/inc/lib/vendor/composer/autoload_classmap.php b/data/web/inc/lib/vendor/composer/autoload_classmap.php index 3885b22a6..417e75a4e 100644 --- a/data/web/inc/lib/vendor/composer/autoload_classmap.php +++ b/data/web/inc/lib/vendor/composer/autoload_classmap.php @@ -6,12 +6,45 @@ $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( + 'AllowDynamicProperties' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php', 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', - 'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'DateError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateError.php', + 'DateException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateException.php', + 'DateInvalidOperationException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php', + 'DateInvalidTimeZoneException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php', + 'DateMalformedIntervalStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php', + 'DateMalformedPeriodStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php', + 'DateMalformedStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php', + 'DateObjectError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php', + 'DateRangeError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php', + 'DelayedTargetValidation' => $vendorDir . '/symfony/polyfill-php85/Resources/stubs/DelayedTargetValidation.php', + 'Deprecated' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php', + 'Filter\\FilterException' => $vendorDir . '/symfony/polyfill-php85/Resources/stubs/Filter/FilterException.php', + 'Filter\\FilterFailedException' => $vendorDir . '/symfony/polyfill-php85/Resources/stubs/Filter/FilterFailedException.php', + 'NoDiscard' => $vendorDir . '/symfony/polyfill-php85/Resources/stubs/NoDiscard.php', + 'Override' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/Override.php', + 'Pdo\\Dblib' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php', + 'Pdo\\Firebird' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php', + 'Pdo\\Mysql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php', + 'Pdo\\Odbc' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php', + 'Pdo\\Pgsql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', + 'Pdo\\Sqlite' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', - 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Random\\BrokenRandomEngineError' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php', + 'Random\\CryptoSafeEngine' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php', + 'Random\\Engine' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/Engine.php', + 'Random\\Engine\\Secure' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php', + 'Random\\RandomError' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php', + 'Random\\RandomException' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php', + 'ReflectionConstant' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', + 'RoundingMode' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', + 'SQLite3Exception' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php', + 'SensitiveParameter' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php', + 'SensitiveParameterValue' => $vendorDir . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Time\\Duration' => $vendorDir . '/symfony/polyfill-time/Resources/stubs/Time/Duration.php', + 'Time\\TimeException' => $vendorDir . '/symfony/polyfill-time/Resources/stubs/Time/TimeException.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); diff --git a/data/web/inc/lib/vendor/composer/autoload_files.php b/data/web/inc/lib/vendor/composer/autoload_files.php index e2cabb90d..971c319de 100644 --- a/data/web/inc/lib/vendor/composer/autoload_files.php +++ b/data/web/inc/lib/vendor/composer/autoload_files.php @@ -6,16 +6,18 @@ $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '5897ea0ac4cccf14d323035e65887801' => $vendorDir . '/symfony/polyfill-php82/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', - '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', - 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', - '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', - 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php', + '2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php', + '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php', + '606a39d89246991a373564698c2d8383' => $vendorDir . '/symfony/polyfill-php85/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', + '23f09fe3194f8c2f70923f90d6702129' => $vendorDir . '/illuminate/collections/functions.php', '60799491728b879e74601d83e38b2cad' => $vendorDir . '/illuminate/collections/helpers.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', - '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php', '89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php', 'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php', diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php index afc9a1dd1..6a01462ca 100644 --- a/data/web/inc/lib/vendor/composer/autoload_psr4.php +++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php @@ -7,12 +7,17 @@ $baseDir = dirname($vendorDir); return array( 'Twig\\' => array($vendorDir . '/twig/twig/src'), - 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Time\\' => array($vendorDir . '/symfony/polyfill-time'), + 'Symfony\\Polyfill\\Php85\\' => array($vendorDir . '/symfony/polyfill-php85'), + 'Symfony\\Polyfill\\Php84\\' => array($vendorDir . '/symfony/polyfill-php84'), + 'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'), + 'Symfony\\Polyfill\\Php82\\' => array($vendorDir . '/symfony/polyfill-php82'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'), 'Stevenmaguire\\OAuth2\\Client\\' => array($vendorDir . '/stevenmaguire/oauth2-keycloak/src'), 'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index f0a06b7cf..19990d8c2 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -7,16 +7,18 @@ namespace Composer\Autoload; class ComposerStaticInit873464e4bd965a3168f133248b1b218b { public static $files = array ( + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '5897ea0ac4cccf14d323035e65887801' => __DIR__ . '/..' . '/symfony/polyfill-php82/bootstrap.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', - '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', - 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', - '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', - 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php', + '2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php', + '9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php', + '606a39d89246991a373564698c2d8383' => __DIR__ . '/..' . '/symfony/polyfill-php85/bootstrap.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', + '23f09fe3194f8c2f70923f90d6702129' => __DIR__ . '/..' . '/illuminate/collections/functions.php', '60799491728b879e74601d83e38b2cad' => __DIR__ . '/..' . '/illuminate/collections/helpers.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', - '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php', '89efb1254ef2d1c5d80096acd12c4098' => __DIR__ . '/..' . '/twig/twig/src/Resources/core.php', 'ffecb95d45175fd40f75be8a23b34f90' => __DIR__ . '/..' . '/twig/twig/src/Resources/debug.php', @@ -25,25 +27,30 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b ); public static $prefixLengthsPsr4 = array ( - 'T' => + 'T' => array ( 'Twig\\' => 5, ), - 'S' => + 'S' => array ( - 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Time\\' => 22, + 'Symfony\\Polyfill\\Php85\\' => 23, + 'Symfony\\Polyfill\\Php84\\' => 23, + 'Symfony\\Polyfill\\Php83\\' => 23, + 'Symfony\\Polyfill\\Php82\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Contracts\\Translation\\' => 30, 'Symfony\\Component\\Translation\\' => 30, + 'Symfony\\Component\\Clock\\' => 24, 'Stevenmaguire\\OAuth2\\Client\\' => 28, ), - 'R' => + 'R' => array ( 'RobThree\\Auth\\' => 14, ), - 'P' => + 'P' => array ( 'Psr\\SimpleCache\\' => 16, 'Psr\\Log\\' => 8, @@ -54,198 +61,218 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b 'PhpMimeMailParser\\' => 18, 'PHPMailer\\PHPMailer\\' => 20, ), - 'M' => + 'M' => array ( 'MatthiasMullie\\PathConverter\\' => 29, 'MatthiasMullie\\Minify\\' => 22, 'Mailcow\\' => 8, ), - 'L' => + 'L' => array ( 'League\\OAuth2\\Client\\' => 21, 'LdapRecord\\' => 11, ), - 'I' => + 'I' => array ( 'Illuminate\\Support\\' => 19, 'Illuminate\\Contracts\\' => 21, ), - 'H' => + 'H' => array ( 'Html2Text\\' => 10, ), - 'G' => + 'G' => array ( 'GuzzleHttp\\Psr7\\' => 16, 'GuzzleHttp\\Promise\\' => 19, 'GuzzleHttp\\' => 11, ), - 'F' => + 'F' => array ( 'Firebase\\JWT\\' => 13, ), - 'D' => + 'D' => array ( 'Ddeboer\\Imap\\' => 13, 'DASPRiD\\Enum\\' => 13, ), - 'C' => + 'C' => array ( 'Carbon\\Doctrine\\' => 16, 'Carbon\\' => 7, ), - 'B' => + 'B' => array ( 'BaconQrCode\\' => 12, ), ); public static $prefixDirsPsr4 = array ( - 'Twig\\' => + 'Twig\\' => array ( 0 => __DIR__ . '/..' . '/twig/twig/src', ), - 'Symfony\\Polyfill\\Php81\\' => + 'Symfony\\Polyfill\\Time\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', + 0 => __DIR__ . '/..' . '/symfony/polyfill-time', ), - 'Symfony\\Polyfill\\Php80\\' => + 'Symfony\\Polyfill\\Php85\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php85', + ), + 'Symfony\\Polyfill\\Php84\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php84', + ), + 'Symfony\\Polyfill\\Php83\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php83', + ), + 'Symfony\\Polyfill\\Php82\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php82', + ), + 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), - 'Symfony\\Polyfill\\Mbstring\\' => + 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), - 'Symfony\\Polyfill\\Ctype\\' => + 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), - 'Symfony\\Contracts\\Translation\\' => + 'Symfony\\Contracts\\Translation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/translation-contracts', ), - 'Symfony\\Component\\Translation\\' => + 'Symfony\\Component\\Translation\\' => array ( 0 => __DIR__ . '/..' . '/symfony/translation', ), - 'Stevenmaguire\\OAuth2\\Client\\' => + 'Symfony\\Component\\Clock\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/clock', + ), + 'Stevenmaguire\\OAuth2\\Client\\' => array ( 0 => __DIR__ . '/..' . '/stevenmaguire/oauth2-keycloak/src', ), - 'RobThree\\Auth\\' => + 'RobThree\\Auth\\' => array ( 0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib', ), - 'Psr\\SimpleCache\\' => + 'Psr\\SimpleCache\\' => array ( 0 => __DIR__ . '/..' . '/psr/simple-cache/src', ), - 'Psr\\Log\\' => + 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/src', ), - 'Psr\\Http\\Message\\' => + 'Psr\\Http\\Message\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-factory/src', 1 => __DIR__ . '/..' . '/psr/http-message/src', ), - 'Psr\\Http\\Client\\' => + 'Psr\\Http\\Client\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-client/src', ), - 'Psr\\Container\\' => + 'Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', ), - 'Psr\\Clock\\' => + 'Psr\\Clock\\' => array ( 0 => __DIR__ . '/..' . '/psr/clock/src', ), - 'PhpMimeMailParser\\' => + 'PhpMimeMailParser\\' => array ( 0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src', ), - 'PHPMailer\\PHPMailer\\' => + 'PHPMailer\\PHPMailer\\' => array ( 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', ), - 'MatthiasMullie\\PathConverter\\' => + 'MatthiasMullie\\PathConverter\\' => array ( 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', ), - 'MatthiasMullie\\Minify\\' => + 'MatthiasMullie\\Minify\\' => array ( 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', ), - 'Mailcow\\' => + 'Mailcow\\' => array ( 0 => __DIR__ . '/../..' . '/mailcow', ), - 'League\\OAuth2\\Client\\' => + 'League\\OAuth2\\Client\\' => array ( 0 => __DIR__ . '/..' . '/league/oauth2-client/src', ), - 'LdapRecord\\' => + 'LdapRecord\\' => array ( 0 => __DIR__ . '/..' . '/directorytree/ldaprecord/src', ), - 'Illuminate\\Support\\' => + 'Illuminate\\Support\\' => array ( 0 => __DIR__ . '/..' . '/illuminate/collections', 1 => __DIR__ . '/..' . '/illuminate/macroable', 2 => __DIR__ . '/..' . '/illuminate/conditionable', ), - 'Illuminate\\Contracts\\' => + 'Illuminate\\Contracts\\' => array ( 0 => __DIR__ . '/..' . '/illuminate/contracts', ), - 'Html2Text\\' => + 'Html2Text\\' => array ( 0 => __DIR__ . '/..' . '/soundasleep/html2text/src', ), - 'GuzzleHttp\\Psr7\\' => + 'GuzzleHttp\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', ), - 'GuzzleHttp\\Promise\\' => + 'GuzzleHttp\\Promise\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', ), - 'GuzzleHttp\\' => + 'GuzzleHttp\\' => array ( 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', ), - 'Firebase\\JWT\\' => + 'Firebase\\JWT\\' => array ( 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', ), - 'Ddeboer\\Imap\\' => + 'Ddeboer\\Imap\\' => array ( 0 => __DIR__ . '/..' . '/ddeboer/imap/src', ), - 'DASPRiD\\Enum\\' => + 'DASPRiD\\Enum\\' => array ( 0 => __DIR__ . '/..' . '/dasprid/enum/src', ), - 'Carbon\\Doctrine\\' => + 'Carbon\\Doctrine\\' => array ( 0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine', ), - 'Carbon\\' => + 'Carbon\\' => array ( 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon', ), - 'BaconQrCode\\' => + 'BaconQrCode\\' => array ( 0 => __DIR__ . '/..' . '/bacon/bacon-qr-code/src', ), ); public static $prefixesPsr0 = array ( - 'O' => + 'O' => array ( - 'OAuth2' => + 'OAuth2' => array ( 0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src', ), @@ -253,12 +280,45 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b ); public static $classMap = array ( + 'AllowDynamicProperties' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php', 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', - 'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'DateError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateError.php', + 'DateException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateException.php', + 'DateInvalidOperationException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php', + 'DateInvalidTimeZoneException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php', + 'DateMalformedIntervalStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php', + 'DateMalformedPeriodStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php', + 'DateMalformedStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php', + 'DateObjectError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php', + 'DateRangeError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php', + 'DelayedTargetValidation' => __DIR__ . '/..' . '/symfony/polyfill-php85/Resources/stubs/DelayedTargetValidation.php', + 'Deprecated' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php', + 'Filter\\FilterException' => __DIR__ . '/..' . '/symfony/polyfill-php85/Resources/stubs/Filter/FilterException.php', + 'Filter\\FilterFailedException' => __DIR__ . '/..' . '/symfony/polyfill-php85/Resources/stubs/Filter/FilterFailedException.php', + 'NoDiscard' => __DIR__ . '/..' . '/symfony/polyfill-php85/Resources/stubs/NoDiscard.php', + 'Override' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/Override.php', + 'Pdo\\Dblib' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php', + 'Pdo\\Firebird' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php', + 'Pdo\\Mysql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php', + 'Pdo\\Odbc' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php', + 'Pdo\\Pgsql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php', + 'Pdo\\Sqlite' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php', 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', - 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', + 'Random\\BrokenRandomEngineError' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php', + 'Random\\CryptoSafeEngine' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php', + 'Random\\Engine' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/Engine.php', + 'Random\\Engine\\Secure' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php', + 'Random\\RandomError' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php', + 'Random\\RandomException' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php', + 'ReflectionConstant' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php', + 'RoundingMode' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php', + 'SQLite3Exception' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php', + 'SensitiveParameter' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php', + 'SensitiveParameterValue' => __DIR__ . '/..' . '/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Time\\Duration' => __DIR__ . '/..' . '/symfony/polyfill-time/Resources/stubs/Time/Duration.php', + 'Time\\TimeException' => __DIR__ . '/..' . '/symfony/polyfill-time/Resources/stubs/Time/TimeException.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index e2d7113bc..53c26aab1 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -59,38 +59,38 @@ }, { "name": "bshaffer/oauth2-server-php", - "version": "v1.11.1", - "version_normalized": "1.11.1.0", + "version": "v1.14.2", + "version_normalized": "1.14.2.0", "source": { "type": "git", "url": "https://github.com/bshaffer/oauth2-server-php.git", - "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa" + "reference": "1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/5a0c8000d4763b276919e2106f54eddda6bc50fa", - "reference": "5a0c8000d4763b276919e2106f54eddda6bc50fa", + "url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6", + "reference": "1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=7.2" }, "require-dev": { - "aws/aws-sdk-php": "~2.8", - "firebase/php-jwt": "~2.2", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^4.0", - "predis/predis": "dev-master", - "thobbs/phpcassa": "dev-master" + "aws/aws-sdk-php": "^2.8", + "firebase/php-jwt": "^6.4", + "phpunit/phpunit": "^7.5||^8.0", + "predis/predis": "^1.1", + "thobbs/phpcassa": "dev-master", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "aws/aws-sdk-php": "~2.8 is required to use DynamoDB storage", - "firebase/php-jwt": "~2.2 is required to use JWT features", + "firebase/php-jwt": "~v6.4 is required to use JWT features", "mongodb/mongodb": "^1.1 is required to use MongoDB storage", "predis/predis": "Required to use Redis storage", "thobbs/phpcassa": "Required to use Cassandra storage" }, - "time": "2018-12-04T00:29:32+00:00", + "time": "2025-03-28T21:13:45+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -116,21 +116,25 @@ "oauth", "oauth2" ], + "support": { + "issues": "https://github.com/bshaffer/oauth2-server-php/issues", + "source": "https://github.com/bshaffer/oauth2-server-php/tree/v1.14.2" + }, "install-path": "../bshaffer/oauth2-server-php" }, { "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", - "version_normalized": "3.2.0.0", + "version": "3.2.1", + "version_normalized": "3.2.1.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", "shasum": "" }, "require": { @@ -144,8 +148,13 @@ "nesbot/carbon": "^2.71.0 || ^3.0.0", "phpunit/phpunit": "^10.3" }, - "time": "2024-02-09T16:56:22+00:00", + "time": "2026-09-06T14:11:38+00:00", "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "installation-source": "dist", "autoload": { "psr-4": { @@ -172,7 +181,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1" }, "funding": [ { @@ -245,35 +254,36 @@ }, { "name": "ddeboer/imap", - "version": "1.13.1", - "version_normalized": "1.13.1.0", + "version": "1.19.0", + "version_normalized": "1.19.0.0", "source": { "type": "git", "url": "https://github.com/ddeboer/imap.git", - "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e" + "reference": "30800b1cfeacc4add5bb418e40a8b6e95a8a04ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ddeboer/imap/zipball/8b772d04b1deadb5df13782fb78c4b648f77496e", - "reference": "8b772d04b1deadb5df13782fb78c4b648f77496e", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/30800b1cfeacc4add5bb418e40a8b6e95a8a04ac", + "reference": "30800b1cfeacc4add5bb418e40a8b6e95a8a04ac", "shasum": "" }, "require": { + "ext-dom": "*", "ext-iconv": "*", "ext-imap": "*", + "ext-libxml": "*", "ext-mbstring": "*", - "php": "^8.0.1" + "php": "~8.2.0 || ~8.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.4.0", - "laminas/laminas-mail": "^2.15.1", - "malukenho/mcbumpface": "^1.1.5", - "phpstan/phpstan": "^1.3.3", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpunit/phpunit": "^9.5.11" + "friendsofphp/php-cs-fixer": "^3.38.2", + "laminas/laminas-mail": "^2.25.1", + "phpstan/phpstan": "^1.10.43", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "phpunit/phpunit": "^10.4.2" }, - "time": "2022-01-10T10:53:05+00:00", + "time": "2023-11-20T14:41:54+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -307,7 +317,7 @@ ], "support": { "issues": "https://github.com/ddeboer/imap/issues", - "source": "https://github.com/ddeboer/imap/tree/1.13.1" + "source": "https://github.com/ddeboer/imap/tree/1.19.0" }, "funding": [ { @@ -323,25 +333,25 @@ }, { "name": "directorytree/ldaprecord", - "version": "v3.8.5", - "version_normalized": "3.8.5.0", + "version": "v3.8.6", + "version_normalized": "3.8.6.0", "source": { "type": "git", "url": "https://github.com/DirectoryTree/LdapRecord.git", - "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65" + "reference": "a1cab9f078eb4756fc6fad58caea1e3d11cad0ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/00e5f088f8c4028d5f398783cccc2e8119a27a65", - "reference": "00e5f088f8c4028d5f398783cccc2e8119a27a65", + "url": "https://api.github.com/repos/DirectoryTree/LdapRecord/zipball/a1cab9f078eb4756fc6fad58caea1e3d11cad0ec", + "reference": "a1cab9f078eb4756fc6fad58caea1e3d11cad0ec", "shasum": "" }, "require": { "ext-iconv": "*", "ext-json": "*", "ext-ldap": "*", - "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "*", "php": ">=8.1", "psr/log": "*", @@ -354,7 +364,7 @@ "phpunit/phpunit": "^9.0", "spatie/ray": "^1.24" }, - "time": "2025-10-06T02:22:34+00:00", + "time": "2026-03-19T20:55:30+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -402,36 +412,47 @@ }, { "name": "firebase/php-jwt", - "version": "v6.5.0", - "version_normalized": "6.5.0.0", + "version": "v7.2.1", + "version_normalized": "7.2.1.0", "source": { "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "0501f5729b9e2448516674aff07c7d1727869b8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", - "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/0501f5729b9e2448516674aff07c7d1727869b8e", + "reference": "0501f5729b9e2448516674aff07c7d1727869b8e", "shasum": "" }, "require": { - "php": "^7.4||^8.0" + "php": "^8.0" }, "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", - "phpspec/prophecy-phpunit": "^2.0", + "guzzlehttp/guzzle": "^7.4||^8.0", + "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", + "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0||^2.0", + "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0" }, "suggest": { "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" }, - "time": "2023-05-12T15:47:07+00:00", + "time": "2026-09-28T17:51:46+00:00", "type": "library", + "extra": { + "component": { + "id": "jwt", + "path": "Jwt", + "entry": "README.md", + "target": "googleapis/php-jwt.git" + } + }, "installation-source": "dist", "autoload": { "psr-4": { @@ -455,48 +476,52 @@ } ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", + "homepage": "https://github.com/googleapis/php-jwt", "keywords": [ "jwt", "php" ], "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.2.1" }, "install-path": "../firebase/php-jwt" }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", - "version_normalized": "7.5.0.0", + "version": "8.3.1", + "version_normalized": "8.3.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "7d9b1f430bb65ac664e120af227bf9a795ad6333" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7d9b1f430bb65ac664e120af227bf9a795ad6333", + "reference": "7d9b1f430bb65ac664e120af227bf9a795ad6333", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", - "php": "^7.2.5 || ^8.0", + "guzzlehttp/promises": "^3.1", + "guzzlehttp/psr7": "^3.2", + "php": "^7.4 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "psr/http-factory": "^1.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "guzzle/client-integration-tests": "4.0.1", + "guzzlehttp/test-server": "^1.1", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -504,22 +529,16 @@ "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, - "time": "2022-08-28T15:39:27+00:00", + "time": "2026-10-25T21:35:34+00:00", "type": "library", "extra": { "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" } }, "installation-source": "dist", "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\": "src/" } @@ -579,7 +598,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/8.3.1" }, "funding": [ { @@ -599,37 +618,37 @@ }, { "name": "guzzlehttp/promises", - "version": "1.5.2", - "version_normalized": "1.5.2.0", + "version": "3.1.0", + "version_normalized": "3.1.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" + "reference": "094d0ec418eb3b1411ea271a180ec95cae6ae933" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "url": "https://api.github.com/repos/guzzle/promises/zipball/094d0ec418eb3b1411ea271a180ec95cae6ae933", + "reference": "094d0ec418eb3b1411ea271a180ec95cae6ae933", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.34" }, - "time": "2022-08-28T14:55:35+00:00", + "time": "2026-10-18T12:48:16+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "installation-source": "dist", "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -666,7 +685,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" + "source": "https://github.com/guzzle/promises/tree/3.1.0" }, "funding": [ { @@ -686,38 +705,41 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.5", - "version_normalized": "2.4.5.0", + "version": "3.2.0", + "version_normalized": "3.2.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66" + "reference": "cdc139396207a5eba10c242d241fb014cb098390" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66", - "reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/cdc139396207a5eba10c242d241fb014cb098390", + "reference": "cdc139396207a5eba10c242d241fb014cb098390", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, - "time": "2023-04-17T16:00:45+00:00", + "time": "2026-10-17T23:57:19+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -785,7 +807,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.5" + "source": "https://github.com/guzzle/psr7/tree/3.2.0" }, "funding": [ { @@ -805,38 +827,43 @@ }, { "name": "illuminate/collections", - "version": "v10.49.0", - "version_normalized": "10.49.0.0", + "version": "v12.70.2", + "version_normalized": "12.70.2.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232" + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", - "reference": "6ae9c74fa92d4e1824d1b346cd435e8eacdc3232", + "url": "https://api.github.com/repos/illuminate/collections/zipball/83313b009c4afb6f02dbc090bdb67809756eefa2", + "reference": "83313b009c4afb6f02dbc090bdb67809756eefa2", "shasum": "" }, "require": { - "illuminate/conditionable": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0", - "php": "^8.1" + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "php": "^8.2", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^6.2)." + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." }, - "time": "2025-09-08T19:05:53+00:00", + "time": "2026-03-11T14:13:25+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "installation-source": "dist", "autoload": { "files": [ + "functions.php", "helpers.php" ], "psr-4": { @@ -863,27 +890,27 @@ }, { "name": "illuminate/conditionable", - "version": "v10.49.0", - "version_normalized": "10.49.0.0", + "version": "v12.70.2", + "version_normalized": "12.70.2.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", - "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f" + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/conditionable/zipball/47c700320b7a419f0d188d111f3bbed978fcbd3f", - "reference": "47c700320b7a419f0d188d111f3bbed978fcbd3f", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/ec677967c1f2faf90b8428919124d2184a4c9b49", + "reference": "ec677967c1f2faf90b8428919124d2184a4c9b49", "shasum": "" }, "require": { - "php": "^8.0.2" + "php": "^8.2" }, - "time": "2025-03-24T11:47:24+00:00", + "time": "2025-05-13T15:08:45+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "installation-source": "dist", @@ -912,29 +939,29 @@ }, { "name": "illuminate/contracts", - "version": "v10.44.0", - "version_normalized": "10.44.0.0", + "version": "v12.70.2", + "version_normalized": "12.70.2.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac" + "reference": "c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", - "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5", + "reference": "c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/simple-cache": "^1.0|^2.0|^3.0" }, - "time": "2024-01-15T18:52:32+00:00", + "time": "2026-06-09T13:20:54+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "installation-source": "dist", @@ -963,27 +990,27 @@ }, { "name": "illuminate/macroable", - "version": "v10.49.0", - "version_normalized": "10.49.0.0", + "version": "v12.70.2", + "version_normalized": "12.70.2.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", - "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27" + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/macroable/zipball/dff667a46ac37b634dcf68909d9d41e94dc97c27", - "reference": "dff667a46ac37b634dcf68909d9d41e94dc97c27", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", + "reference": "e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, - "time": "2023-06-05T12:46:42+00:00", + "time": "2026-03-30T19:05:19+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "installation-source": "dist", @@ -1012,37 +1039,33 @@ }, { "name": "league/oauth2-client", - "version": "2.7.0", - "version_normalized": "2.7.0.0", + "version": "2.9.1", + "version_normalized": "2.9.1.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-client.git", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", - "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0 || ^7.0", - "paragonie/random_compat": "^1 || ^2 || ^9.99", - "php": "^5.6 || ^7.0 || ^8.0" + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.8.2 || ^8.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.6.3 || ^3.0", + "php": "^7.1 || >=8.0.0 <8.6.0" }, "require-dev": { "mockery/mockery": "^1.3.5", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", - "squizlabs/php_codesniffer": "^2.3 || ^3.0" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" }, - "time": "2023-04-16T18:19:15+00:00", + "time": "2026-09-16T13:14:31+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.0.x-dev" - } - }, "installation-source": "dist", "autoload": { "psr-4": { @@ -1079,23 +1102,23 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-client/issues", - "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.1" }, "install-path": "../league/oauth2-client" }, { "name": "matthiasmullie/minify", - "version": "1.3.66", - "version_normalized": "1.3.66.0", + "version": "1.3.75", + "version_normalized": "1.3.75.0", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6" + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", - "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671", + "reference": "76ba4a5f555fd7bf4aa408af608e991569076671", "shasum": "" }, "require": { @@ -1104,14 +1127,14 @@ "php": ">=5.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2.0", - "matthiasmullie/scrapbook": "dev-master", + "friendsofphp/php-cs-fixer": ">=2.0", + "matthiasmullie/scrapbook": ">=1.3", "phpunit/phpunit": ">=4.8" }, "suggest": { "psr/cache-implementation": "Cache implementation to use with Minify::cache" }, - "time": "2021-01-06T15:18:10+00:00", + "time": "2025-06-25T09:56:19+00:00", "bin": [ "bin/minifycss", "bin/minifyjs" @@ -1131,12 +1154,12 @@ { "name": "Matthias Mullie", "email": "minify@mullie.eu", - "homepage": "http://www.mullie.eu", + "homepage": "https://www.mullie.eu", "role": "Developer" } ], "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", - "homepage": "http://www.minifier.org", + "homepage": "https://github.com/matthiasmullie/minify", "keywords": [ "JS", "css", @@ -1146,19 +1169,11 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.66" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.75" }, "funding": [ { - "url": "https://github.com/[user1", - "type": "github" - }, - { - "url": "https://github.com/matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g.", - "type": "github" - }, - { - "url": "https://github.com/user2", + "url": "https://github.com/matthiasmullie", "type": "github" } ], @@ -1271,54 +1286,49 @@ }, { "name": "nesbot/carbon", - "version": "2.72.3", - "version_normalized": "2.72.3.0", + "version": "3.15.1", + "version_normalized": "3.15.1.0", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83", - "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ce0e5636fae7d1e99856eb70cae4c719a284aa08", + "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "*", + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", - "php": "^7.1.8 || ^8.0", + "php": "^8.1", "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + "symfony/polyfill-time": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" }, "provide": { "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", - "doctrine/orm": "^2.7 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.0", - "kylekatarnls/multi-tester": "^2.0", - "ondrejmirtes/better-reflection": "*", - "phpmd/phpmd": "^2.9", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.99 || ^1.7.14", - "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", - "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", - "squizlabs/php_codesniffer": "^3.4" + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^3.0@dev", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, - "time": "2024-01-25T10:35:09+00:00", + "time": "2026-10-25T14:32:48+00:00", "bin": [ "bin/carbon" ], "type": "library", "extra": { - "branch-alias": { - "dev-3.x": "3.x-dev", - "dev-master": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -1328,6 +1338,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "installation-source": "dist", @@ -1352,16 +1366,16 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", - "issues": "https://github.com/briannesbitt/Carbon/issues", - "source": "https://github.com/briannesbitt/Carbon" + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" }, "funding": [ { @@ -1379,72 +1393,19 @@ ], "install-path": "../nesbot/carbon" }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "version_normalized": "9.99.100.0", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "time": "2020-10-15T08:29:30+00:00", - "type": "library", - "installation-source": "dist", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "install-path": "../paragonie/random_compat" - }, { "name": "php-mime-mail-parser/php-mime-mail-parser", - "version": "7.0.0", - "version_normalized": "7.0.0.0", + "version": "7.1.2", + "version_normalized": "7.1.2.0", "source": { "type": "git", "url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git", - "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d" + "reference": "81d84bbc6a73b913110e155e98059526825c7ee3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/9d09a017f3f103fec8456211a4a538b80e0eca0d", - "reference": "9d09a017f3f103fec8456211a4a538b80e0eca0d", + "url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/81d84bbc6a73b913110e155e98059526825c7ee3", + "reference": "81d84bbc6a73b913110e155e98059526825c7ee3", "shasum": "" }, "require": { @@ -1457,10 +1418,10 @@ }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^8.0", + "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5" }, - "time": "2021-02-25T17:21:57+00:00", + "time": "2022-04-16T07:56:53+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1516,7 +1477,7 @@ ], "support": { "issues": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/issues", - "source": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/tree/7.0.0" + "source": "https://github.com/php-mime-mail-parser/php-mime-mail-parser/tree/7.1.2" }, "funding": [ { @@ -1528,17 +1489,17 @@ }, { "name": "phpmailer/phpmailer", - "version": "v6.6.0", - "version_normalized": "6.6.0.0", + "version": "v6.12.0", + "version_normalized": "6.12.0.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1" + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e43bac82edc26ca04b36143a48bde1c051cfd5b1", - "reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d1ac35d784bf9f5e61b424901d5a014967f15b12", + "reference": "d1ac35d784bf9f5e61b424901d5a014967f15b12", "shasum": "" }, "require": { @@ -1548,24 +1509,27 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3.1", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, - "time": "2022-02-28T15:31:21+00:00", + "time": "2025-10-15T16:49:08+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1597,7 +1561,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.0" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.12.0" }, "funding": [ { @@ -1716,24 +1680,24 @@ }, { "name": "psr/http-client", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "version": "1.0.3", + "version_normalized": "1.0.3.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, - "time": "2020-06-29T06:28:15+00:00", + "time": "2023-09-23T14:17:50+00:00", "type": "library", "extra": { "branch-alias": { @@ -1753,7 +1717,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -1765,30 +1729,30 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" }, "install-path": "../psr/http-client" }, { "name": "psr/http-factory", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "version": "1.1.0", + "version_normalized": "1.1.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, - "time": "2019-04-30T12:38:16+00:00", + "time": "2024-04-15T12:06:14+00:00", "type": "library", "extra": { "branch-alias": { @@ -1808,10 +1772,10 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1823,33 +1787,33 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory" }, "install-path": "../psr/http-factory" }, { "name": "psr/http-message", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "version": "2.0", + "version_normalized": "2.0.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, - "time": "2016-08-06T14:39:51+00:00", + "time": "2023-04-04T09:54:51+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "installation-source": "dist", @@ -1865,7 +1829,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -1879,29 +1843,29 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, "install-path": "../psr/http-message" }, { "name": "psr/log", - "version": "3.0.0", - "version_normalized": "3.0.0.0", + "version": "3.0.2", + "version_normalized": "3.0.2.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { "php": ">=8.0.0" }, - "time": "2021-07-14T16:46:02+00:00", + "time": "2024-09-11T13:17:53+00:00", "type": "library", "extra": { "branch-alias": { @@ -1932,33 +1896,33 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, "install-path": "../psr/log" }, { "name": "psr/simple-cache", - "version": "2.0.0", - "version_normalized": "2.0.0.0", + "version": "3.0.0", + "version_normalized": "3.0.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { "php": ">=8.0.0" }, - "time": "2021-10-29T13:22:09+00:00", + "time": "2021-10-29T13:26:27+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "installation-source": "dist", @@ -1986,70 +1950,23 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, "install-path": "../psr/simple-cache" }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "version_normalized": "3.0.3.0", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "time": "2019-03-08T08:55:37+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "install-path": "../ralouphie/getallheaders" - }, { "name": "robthree/twofactorauth", - "version": "1.8.1", - "version_normalized": "1.8.1.0", + "version": "1.8.2", + "version_normalized": "1.8.2.0", "source": { "type": "git", "url": "https://github.com/RobThree/TwoFactorAuth.git", - "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b" + "reference": "65681de5a324eae05140ac58b08648a60212afc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5afcb45282f1c75562a48d479ecd1732c9bdb11b", - "reference": "5afcb45282f1c75562a48d479ecd1732c9bdb11b", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/65681de5a324eae05140ac58b08648a60212afc0", + "reference": "65681de5a324eae05140ac58b08648a60212afc0", "shasum": "" }, "require": { @@ -2063,7 +1980,7 @@ "bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider", "endroid/qr-code": "Needed for EndroidQrCodeProvider" }, - "time": "2021-10-20T12:19:55+00:00", + "time": "2022-03-22T16:11:07+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2165,30 +2082,31 @@ }, { "name": "stevenmaguire/oauth2-keycloak", - "version": "4.0.0", - "version_normalized": "4.0.0.0", + "version": "6.1.1", + "version_normalized": "6.1.1.0", "source": { "type": "git", "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", - "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d" + "reference": "31bb3b1fa15b444212ed43facc898fafc7c2707a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", - "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/31bb3b1fa15b444212ed43facc898fafc7c2707a", + "reference": "31bb3b1fa15b444212ed43facc898fafc7c2707a", "shasum": "" }, "require": { - "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0", - "league/oauth2-client": "^2.0", - "php": "~7.2 || ~8.0" + "firebase/php-jwt": "^7.0", + "league/oauth2-client": "^2.8", + "php": "^8.0" }, "require-dev": { - "mockery/mockery": "~1.5.0", + "mockery/mockery": "^1.6", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "~9.6.4", "squizlabs/php_codesniffer": "~3.7.0" }, - "time": "2023-03-14T09:43:47+00:00", + "time": "2026-03-30T07:32:03+00:00", "type": "library", "extra": { "branch-alias": { @@ -2223,37 +2141,118 @@ ], "support": { "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", - "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0" + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/6.1.1" }, "install-path": "../stevenmaguire/oauth2-keycloak" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", - "version_normalized": "3.5.0.0", + "name": "symfony/clock", + "version": "v7.4.21", + "version_normalized": "7.4.21.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "url": "https://github.com/symfony/clock.git", + "reference": "07437f612dca89c1894c1db04491002b1e798f03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/clock/zipball/07437f612dca89c1894c1db04491002b1e798f03", + "reference": "07437f612dca89c1894c1db04491002b1e798f03", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "time": "2026-10-12T14:31:36+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/clock" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "version_normalized": "3.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { "php": ">=8.1" }, - "time": "2024-04-18T09:32:20+00:00", + "time": "2026-06-05T06:23:12+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "installation-source": "dist", @@ -2279,7 +2278,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -2290,6 +2289,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2299,21 +2302,21 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.24.0", - "version_normalized": "1.24.0.0", + "version": "v1.37.0", + "version_normalized": "1.37.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -2321,15 +2324,12 @@ "suggest": { "ext-ctype": "For best performance" }, - "time": "2021-10-20T20:35:02+00:00", + "time": "2026-04-10T16:19:22+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -2364,7 +2364,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -2375,6 +2375,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2384,21 +2388,22 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.43.3", + "version_normalized": "1.43.3.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/122889ed24dc000165bcbfd77ec412f15cbdfc6a", + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -2406,12 +2411,12 @@ "suggest": { "ext-mbstring": "For best performance" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2026-10-28T09:35:49+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -2447,7 +2452,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.3" }, "funding": [ { @@ -2458,6 +2463,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2467,28 +2476,28 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2026-09-25T15:50:05+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -2530,7 +2539,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { @@ -2541,6 +2550,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2549,29 +2562,29 @@ "install-path": "../symfony/polyfill-php80" }, { - "name": "symfony/polyfill-php81", - "version": "v1.31.0", - "version_normalized": "1.31.0.0", + "name": "symfony/polyfill-php82", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/843bbe8b2a7934307b749751eede0f111f5d1cfc", + "reference": "843bbe8b2a7934307b749751eede0f111f5d1cfc", "shasum": "" }, "require": { "php": ">=7.2" }, - "time": "2024-09-09T11:45:10+00:00", + "time": "2026-09-25T15:50:05+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -2580,7 +2593,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" + "Symfony\\Polyfill\\Php82\\": "" }, "classmap": [ "Resources/stubs" @@ -2600,7 +2613,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2609,7 +2622,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.43.0" }, "funding": [ { @@ -2620,63 +2633,403 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "install-path": "../symfony/polyfill-php81" + "install-path": "../symfony/polyfill-php82" }, { - "name": "symfony/translation", - "version": "v6.4.3", - "version_normalized": "6.4.3.0", + "name": "symfony/polyfill-php83", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "637c51191b6b184184bbf98937702bcf554f7d04" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/637c51191b6b184184bbf98937702bcf554f7d04", - "reference": "637c51191b6b184184bbf98937702bcf554f7d04", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/80ccff923a8d61f73ebe9da0aa94e25232f004ce", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=7.2" + }, + "time": "2026-09-25T15:50:05+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php83" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2026-09-23T22:01:09+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php84" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "version_normalized": "1.43.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "time": "2026-09-25T15:46:47+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php85" + }, + { + "name": "symfony/polyfill-time", + "version": "v1.42.0", + "version_normalized": "1.42.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-time.git", + "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-time/zipball/ac9f1ed802dd58f37e720388130c088434aa09fc", + "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2026-08-07T13:08:13+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Time\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Philippe Nyamagana Butera", + "email": "nyamsprod@gmail.com" + }, + { + "name": "Tim Düsterhus", + "email": "tim@tideways-gmbh.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Time API added to PHP 8.6", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "duration", + "polyfill", + "portable", + "shim", + "time" + ], + "support": { + "source": "https://github.com/symfony/polyfill-time/tree/v1.42.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-time" + }, + { + "name": "symfony/translation", + "version": "v7.4.17", + "version_normalized": "7.4.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/2ee1e4a3b32a528a642babe041ff7c440b213b4b", + "reference": "2ee1e4a3b32a528a642babe041ff7c440b213b4b", + "shasum": "" + }, + "require": { + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5|^3.0" + "symfony/translation-contracts": "^2.5.3|^3.3" }, "conflict": { - "symfony/config": "<5.4", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.4", "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<5.4", - "symfony/yaml": "<5.4" + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0|^8.0" }, - "time": "2024-01-29T13:11:52+00:00", + "time": "2026-08-21T17:40:08+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2707,7 +3060,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.3" + "source": "https://github.com/symfony/translation/tree/v7.4.17" }, "funding": [ { @@ -2718,6 +3071,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2727,31 +3084,31 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.4.1", - "version_normalized": "3.4.1.0", + "version": "v3.7.1", + "version_normalized": "3.7.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "06450585bf65e978026bda220cdebca3f867fde7" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", - "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { "php": ">=8.1" }, - "time": "2023-12-26T14:02:43+00:00", + "time": "2026-06-05T06:23:12+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "installation-source": "dist", @@ -2788,7 +3145,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -2799,6 +3156,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2808,31 +3169,32 @@ }, { "name": "twig/twig", - "version": "v3.14.0", - "version_normalized": "3.14.0.0", + "version": "v3.30.0", + "version_normalized": "3.30.0.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + "reference": "8c737079b726af72ff8ef3c595be9f6a810ea1ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8c737079b726af72ff8ef3c595be9f6a810ea1ef", + "reference": "8c737079b726af72ff8ef3c595be9f6a810ea1ef", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php81": "^1.29" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, - "time": "2024-09-09T17:55:12+00:00", + "time": "2026-09-25T13:20:01+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2874,7 +3236,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + "source": "https://github.com/twigphp/Twig/tree/v3.30.0" }, "funding": [ { diff --git a/data/web/inc/lib/vendor/composer/installed.php b/data/web/inc/lib/vendor/composer/installed.php index 15a1aa89f..25474f826 100644 --- a/data/web/inc/lib/vendor/composer/installed.php +++ b/data/web/inc/lib/vendor/composer/installed.php @@ -29,18 +29,18 @@ 'dev_requirement' => false, ), 'bshaffer/oauth2-server-php' => array( - 'pretty_version' => 'v1.11.1', - 'version' => '1.11.1.0', - 'reference' => '5a0c8000d4763b276919e2106f54eddda6bc50fa', + 'pretty_version' => 'v1.14.2', + 'version' => '1.14.2.0', + 'reference' => '1c715c2d2f71254d1c683b8c89a0f16b61ccd0d6', 'type' => 'library', 'install_path' => __DIR__ . '/../bshaffer/oauth2-server-php', 'aliases' => array(), 'dev_requirement' => false, ), 'carbonphp/carbon-doctrine-types' => array( - 'pretty_version' => '3.2.0', - 'version' => '3.2.0.0', - 'reference' => '18ba5ddfec8976260ead6e866180bd5d2f71aa1d', + 'pretty_version' => '3.2.1', + 'version' => '3.2.1.0', + 'reference' => '5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc', 'type' => 'library', 'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types', 'aliases' => array(), @@ -56,18 +56,18 @@ 'dev_requirement' => false, ), 'ddeboer/imap' => array( - 'pretty_version' => '1.13.1', - 'version' => '1.13.1.0', - 'reference' => '8b772d04b1deadb5df13782fb78c4b648f77496e', + 'pretty_version' => '1.19.0', + 'version' => '1.19.0.0', + 'reference' => '30800b1cfeacc4add5bb418e40a8b6e95a8a04ac', 'type' => 'library', 'install_path' => __DIR__ . '/../ddeboer/imap', 'aliases' => array(), 'dev_requirement' => false, ), 'directorytree/ldaprecord' => array( - 'pretty_version' => 'v3.8.5', - 'version' => '3.8.5.0', - 'reference' => '00e5f088f8c4028d5f398783cccc2e8119a27a65', + 'pretty_version' => 'v3.8.6', + 'version' => '3.8.6.0', + 'reference' => 'a1cab9f078eb4756fc6fad58caea1e3d11cad0ec', 'type' => 'library', 'install_path' => __DIR__ . '/../directorytree/ldaprecord', 'aliases' => array(), @@ -80,90 +80,90 @@ ), ), 'firebase/php-jwt' => array( - 'pretty_version' => 'v6.5.0', - 'version' => '6.5.0.0', - 'reference' => 'e94e7353302b0c11ec3cfff7180cd0b1743975d2', + 'pretty_version' => 'v7.2.1', + 'version' => '7.2.1.0', + 'reference' => '0501f5729b9e2448516674aff07c7d1727869b8e', 'type' => 'library', 'install_path' => __DIR__ . '/../firebase/php-jwt', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/guzzle' => array( - 'pretty_version' => '7.5.0', - 'version' => '7.5.0.0', - 'reference' => 'b50a2a1251152e43f6a37f0fa053e730a67d25ba', + 'pretty_version' => '8.3.1', + 'version' => '8.3.1.0', + 'reference' => '7d9b1f430bb65ac664e120af227bf9a795ad6333', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/promises' => array( - 'pretty_version' => '1.5.2', - 'version' => '1.5.2.0', - 'reference' => 'b94b2807d85443f9719887892882d0329d1e2598', + 'pretty_version' => '3.1.0', + 'version' => '3.1.0.0', + 'reference' => '094d0ec418eb3b1411ea271a180ec95cae6ae933', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/promises', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/psr7' => array( - 'pretty_version' => '2.4.5', - 'version' => '2.4.5.0', - 'reference' => '0454e12ef0cd597ccd2adb036f7bda4e7fface66', + 'pretty_version' => '3.2.0', + 'version' => '3.2.0.0', + 'reference' => 'cdc139396207a5eba10c242d241fb014cb098390', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), 'dev_requirement' => false, ), 'illuminate/collections' => array( - 'pretty_version' => 'v10.49.0', - 'version' => '10.49.0.0', - 'reference' => '6ae9c74fa92d4e1824d1b346cd435e8eacdc3232', + 'pretty_version' => 'v12.70.2', + 'version' => '12.70.2.0', + 'reference' => '83313b009c4afb6f02dbc090bdb67809756eefa2', 'type' => 'library', 'install_path' => __DIR__ . '/../illuminate/collections', 'aliases' => array(), 'dev_requirement' => false, ), 'illuminate/conditionable' => array( - 'pretty_version' => 'v10.49.0', - 'version' => '10.49.0.0', - 'reference' => '47c700320b7a419f0d188d111f3bbed978fcbd3f', + 'pretty_version' => 'v12.70.2', + 'version' => '12.70.2.0', + 'reference' => 'ec677967c1f2faf90b8428919124d2184a4c9b49', 'type' => 'library', 'install_path' => __DIR__ . '/../illuminate/conditionable', 'aliases' => array(), 'dev_requirement' => false, ), 'illuminate/contracts' => array( - 'pretty_version' => 'v10.44.0', - 'version' => '10.44.0.0', - 'reference' => '8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac', + 'pretty_version' => 'v12.70.2', + 'version' => '12.70.2.0', + 'reference' => 'c16fd7ba7d8e6b8f336639ceb6b7e1ff6ed0efb5', 'type' => 'library', 'install_path' => __DIR__ . '/../illuminate/contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'illuminate/macroable' => array( - 'pretty_version' => 'v10.49.0', - 'version' => '10.49.0.0', - 'reference' => 'dff667a46ac37b634dcf68909d9d41e94dc97c27', + 'pretty_version' => 'v12.70.2', + 'version' => '12.70.2.0', + 'reference' => 'e295d62d89dcdb87e2b1bd70dd14d074a0ed73cc', 'type' => 'library', 'install_path' => __DIR__ . '/../illuminate/macroable', 'aliases' => array(), 'dev_requirement' => false, ), 'league/oauth2-client' => array( - 'pretty_version' => '2.7.0', - 'version' => '2.7.0.0', - 'reference' => '160d6274b03562ebeb55ed18399281d8118b76c8', + 'pretty_version' => '2.9.1', + 'version' => '2.9.1.0', + 'reference' => '8cedfef9d01a8d1fd2ecfabb41734b17592c4b71', 'type' => 'library', 'install_path' => __DIR__ . '/../league/oauth2-client', 'aliases' => array(), 'dev_requirement' => false, ), 'matthiasmullie/minify' => array( - 'pretty_version' => '1.3.66', - 'version' => '1.3.66.0', - 'reference' => '45fd3b0f1dfa2c965857c6d4a470bea52adc31a6', + 'pretty_version' => '1.3.75', + 'version' => '1.3.75.0', + 'reference' => '76ba4a5f555fd7bf4aa408af608e991569076671', 'type' => 'library', 'install_path' => __DIR__ . '/../matthiasmullie/minify', 'aliases' => array(), @@ -194,36 +194,27 @@ 'dev_requirement' => false, ), 'nesbot/carbon' => array( - 'pretty_version' => '2.72.3', - 'version' => '2.72.3.0', - 'reference' => '0c6fd108360c562f6e4fd1dedb8233b423e91c83', + 'pretty_version' => '3.15.1', + 'version' => '3.15.1.0', + 'reference' => 'ce0e5636fae7d1e99856eb70cae4c719a284aa08', 'type' => 'library', 'install_path' => __DIR__ . '/../nesbot/carbon', 'aliases' => array(), 'dev_requirement' => false, ), - 'paragonie/random_compat' => array( - 'pretty_version' => 'v9.99.100', - 'version' => '9.99.100.0', - 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', - 'type' => 'library', - 'install_path' => __DIR__ . '/../paragonie/random_compat', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'php-mime-mail-parser/php-mime-mail-parser' => array( - 'pretty_version' => '7.0.0', - 'version' => '7.0.0.0', - 'reference' => '9d09a017f3f103fec8456211a4a538b80e0eca0d', + 'pretty_version' => '7.1.2', + 'version' => '7.1.2.0', + 'reference' => '81d84bbc6a73b913110e155e98059526825c7ee3', 'type' => 'library', 'install_path' => __DIR__ . '/../php-mime-mail-parser/php-mime-mail-parser', 'aliases' => array(), 'dev_requirement' => false, ), 'phpmailer/phpmailer' => array( - 'pretty_version' => 'v6.6.0', - 'version' => '6.6.0.0', - 'reference' => 'e43bac82edc26ca04b36143a48bde1c051cfd5b1', + 'pretty_version' => 'v6.12.0', + 'version' => '6.12.0.0', + 'reference' => 'd1ac35d784bf9f5e61b424901d5a014967f15b12', 'type' => 'library', 'install_path' => __DIR__ . '/../phpmailer/phpmailer', 'aliases' => array(), @@ -254,9 +245,9 @@ 'dev_requirement' => false, ), 'psr/http-client' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621', + 'pretty_version' => '1.0.3', + 'version' => '1.0.3.0', + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-client', 'aliases' => array(), @@ -269,9 +260,9 @@ ), ), 'psr/http-factory' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be', + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-factory', 'aliases' => array(), @@ -280,13 +271,13 @@ 'psr/http-factory-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '1.1', ), ), 'psr/http-message' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'pretty_version' => '2.0', + 'version' => '2.0.0.0', + 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-message', 'aliases' => array(), @@ -295,40 +286,31 @@ 'psr/http-message-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '2.0', ), ), 'psr/log' => array( - 'pretty_version' => '3.0.0', - 'version' => '3.0.0.0', - 'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001', + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => false, ), 'psr/simple-cache' => array( - 'pretty_version' => '2.0.0', - 'version' => '2.0.0.0', - 'reference' => '8707bf3cea6f710bf6ef05491234e3ab06f6432a', + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/simple-cache', 'aliases' => array(), 'dev_requirement' => false, ), - 'ralouphie/getallheaders' => array( - 'pretty_version' => '3.0.3', - 'version' => '3.0.3.0', - 'reference' => '120b605dfeb996808c31b6477290a714d356e822', - 'type' => 'library', - 'install_path' => __DIR__ . '/../ralouphie/getallheaders', - 'aliases' => array(), - 'dev_requirement' => false, - ), 'robthree/twofactorauth' => array( - 'pretty_version' => '1.8.1', - 'version' => '1.8.1.0', - 'reference' => '5afcb45282f1c75562a48d479ecd1732c9bdb11b', + 'pretty_version' => '1.8.2', + 'version' => '1.8.2.0', + 'reference' => '65681de5a324eae05140ac58b08648a60212afc0', 'type' => 'library', 'install_path' => __DIR__ . '/../robthree/twofactorauth', 'aliases' => array(), @@ -344,72 +326,117 @@ 'dev_requirement' => false, ), 'stevenmaguire/oauth2-keycloak' => array( - 'pretty_version' => '4.0.0', - 'version' => '4.0.0.0', - 'reference' => '05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d', + 'pretty_version' => '6.1.1', + 'version' => '6.1.1.0', + 'reference' => '31bb3b1fa15b444212ed43facc898fafc7c2707a', 'type' => 'library', 'install_path' => __DIR__ . '/../stevenmaguire/oauth2-keycloak', 'aliases' => array(), 'dev_requirement' => false, ), + 'symfony/clock' => array( + 'pretty_version' => 'v7.4.21', + 'version' => '7.4.21.0', + 'reference' => '07437f612dca89c1894c1db04491002b1e798f03', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/clock', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'symfony/deprecation-contracts' => array( - 'pretty_version' => 'v3.5.0', - 'version' => '3.5.0.0', - 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', + 'pretty_version' => 'v3.7.1', + 'version' => '3.7.1.0', + 'reference' => 'f3202fa1b5097b0af062dc978b32ecf63404e31d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( - 'pretty_version' => 'v1.24.0', - 'version' => '1.24.0.0', - 'reference' => '30885182c981ab175d4d034db0f6f469898070ab', + 'pretty_version' => 'v1.37.0', + 'version' => '1.37.0.0', + 'reference' => '141046a8f9477948ff284fa65be2095baafb94f2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', + 'pretty_version' => 'v1.43.3', + 'version' => '1.43.3.0', + 'reference' => '122889ed24dc000165bcbfd77ec412f15cbdfc6a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), - 'symfony/polyfill-php81' => array( - 'pretty_version' => 'v1.31.0', - 'version' => '1.31.0.0', - 'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c', + 'symfony/polyfill-php82' => array( + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '843bbe8b2a7934307b749751eede0f111f5d1cfc', 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'install_path' => __DIR__ . '/../symfony/polyfill-php82', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php83' => array( + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '80ccff923a8d61f73ebe9da0aa94e25232f004ce', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php83', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php84' => array( + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '22c6548499cbe5c5bb072fc0c42312dff062a015', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php84', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php85' => array( + 'pretty_version' => 'v1.43.0', + 'version' => '1.43.0.0', + 'reference' => '86a42cbcc07adc331481a5ab310a4024bfc227f6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php85', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-time' => array( + 'pretty_version' => 'v1.42.0', + 'version' => '1.42.0.0', + 'reference' => 'ac9f1ed802dd58f37e720388130c088434aa09fc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-time', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/translation' => array( - 'pretty_version' => 'v6.4.3', - 'version' => '6.4.3.0', - 'reference' => '637c51191b6b184184bbf98937702bcf554f7d04', + 'pretty_version' => 'v7.4.17', + 'version' => '7.4.17.0', + 'reference' => '2ee1e4a3b32a528a642babe041ff7c440b213b4b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/translation', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/translation-contracts' => array( - 'pretty_version' => 'v3.4.1', - 'version' => '3.4.1.0', - 'reference' => '06450585bf65e978026bda220cdebca3f867fde7', + 'pretty_version' => 'v3.7.1', + 'version' => '3.7.1.0', + 'reference' => 'ccb206b98faccc511ebae8e5fad50f2dc0b30621', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/translation-contracts', 'aliases' => array(), @@ -422,9 +449,9 @@ ), ), 'twig/twig' => array( - 'pretty_version' => 'v3.14.0', - 'version' => '3.14.0.0', - 'reference' => '126b2c97818dbff0cdf3fbfc881aedb3d40aae72', + 'pretty_version' => 'v3.30.0', + 'version' => '3.30.0.0', + 'reference' => '8c737079b726af72ff8ef3c595be9f6a810ea1ef', 'type' => 'library', 'install_path' => __DIR__ . '/../twig/twig', 'aliases' => array(), diff --git a/data/web/inc/lib/vendor/composer/platform_check.php b/data/web/inc/lib/vendor/composer/platform_check.php index 4c3a5d68f..14bf88da3 100644 --- a/data/web/inc/lib/vendor/composer/platform_check.php +++ b/data/web/inc/lib/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 80100)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 80200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { @@ -19,8 +19,7 @@ if ($issues) { echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; } } - trigger_error( - 'Composer detected issues in your platform: ' . implode(' ', $issues), - E_USER_ERROR + throw new \RuntimeException( + 'Composer detected issues in your platform: ' . implode(' ', $issues) ); } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/.php-cs-fixer.php b/data/web/inc/lib/vendor/ddeboer/imap/.php-cs-fixer.php index 22b4ada28..11bbc9bd9 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/.php-cs-fixer.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/.php-cs-fixer.php @@ -17,7 +17,7 @@ return (new PhpCsFixer\Config()) 'binary_operator_spaces' => ['default' => 'align_single_space'], 'blank_line_before_statement' => true, 'class_definition' => ['single_item_single_line' => true], - 'compact_nullable_typehint' => true, + 'compact_nullable_type_declaration' => true, 'concat_space' => ['spacing' => 'one'], 'echo_tag_syntax' => ['format' => 'long'], 'error_suppression' => false, @@ -39,7 +39,7 @@ return (new PhpCsFixer\Config()) 'no_null_property_initialization' => true, 'no_php4_constructor' => true, 'no_superfluous_elseif' => true, - 'no_unneeded_curly_braces' => true, + 'no_unneeded_braces' => true, 'no_unneeded_final_method' => true, 'no_unreachable_default_argument_value' => true, 'no_useless_else' => true, @@ -49,7 +49,7 @@ return (new PhpCsFixer\Config()) 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_strict' => true, 'php_unit_test_annotation' => true, - 'php_unit_test_case_static_method_calls' => true, + 'php_unit_test_case_static_method_calls' => false, 'php_unit_test_class_requires_covers' => false, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_order' => true, diff --git a/data/web/inc/lib/vendor/ddeboer/imap/README.md b/data/web/inc/lib/vendor/ddeboer/imap/README.md index c57ab01fc..8fea97824 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/README.md +++ b/data/web/inc/lib/vendor/ddeboer/imap/README.md @@ -2,12 +2,12 @@ [![Latest Stable Version](https://img.shields.io/packagist/v/ddeboer/imap.svg)](https://packagist.org/packages/ddeboer/imap) [![Downloads](https://img.shields.io/packagist/dt/ddeboer/imap.svg)](https://packagist.org/packages/ddeboer/imap) -[![Integrate](https://github.com/ddeboer/imap/workflows/Integrate/badge.svg?branch=master)](https://github.com/ddeboer/imap/actions) +[![Integrate](https://github.com/ddeboer/imap/workflows/CI/badge.svg)](https://github.com/ddeboer/imap/actions) [![Code Coverage](https://codecov.io/gh/ddeboer/imap/coverage.svg?branch=master)](https://codecov.io/gh/ddeboer/imap?branch=master) A PHP IMAP library to read and process e-mails over IMAP protocol, built with robust Object-Oriented architecture. -This library requires PHP >= 7.4 with [IMAP](https://www.php.net/manual/en/book.imap.php), +This library requires PHP >= 8.2 with [IMAP](https://www.php.net/manual/en/book.imap.php), [iconv](https://www.php.net/manual/en/book.iconv.php) and [Multibyte String](https://www.php.net/manual/en/book.mbstring.php) extensions installed. @@ -157,7 +157,7 @@ By the way most of the common search criteria are available and functioning, bro References: 1. https://stackoverflow.com/questions/36356715/imap-search-unknown-search-criterion-or -1. imap-2007f.tar.gz: `./src/c-client/mail.c` and `./docs/internal.txt` +2. imap-2007f.tar.gz: `./src/c-client/mail.c` and `./docs/internal.txt` #### Message Properties and Operations @@ -182,19 +182,29 @@ $message->isDraft(); $message->isSeen(); ``` -Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Ddeboer/Imap/Message/Headers.php) object: +Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Message/Headers.php) object: ```php $message->getHeaders(); ``` -Get message body as HTML or plain text: +Get message body as HTML or plain text (only first part): ```php $message->getBodyHtml(); // Content of text/html part, if present $message->getBodyText(); // Content of text/plain part, if present ``` + +Get complete body (all parts): + +```php +$body = $message->getCompleteBodyHtml(); // Content of text/html part, if present +if ($body === null) { // If body is null, there are no HTML parts, so let's try getting the text body + $body = $message->getCompleteBodyText(); // Content of text/plain part, if present +} +``` + Reading the message body keeps the message as unseen. If you want to mark the message as seen: @@ -284,24 +294,12 @@ Mockability is granted by interfaces present for each API. Dig into [MockabilityTest](tests/MockabilityTest.php) for an example of a mocked workflow. -## Running the Tests +## Contributing: run the build locally -This library is functionally tested on [Travis CI](https://travis-ci.org/ddeboer/imap) -against a local Dovecot server. +Docker is needed to run the build on your computer. -If you have your own IMAP (test) account, you can run the tests locally by -providing your IMAP credentials: +First command you need to run is `make start-imap-server`, which starts an IMAP server locally. -```bash -$ composer install -$ IMAP_SERVER_NAME="my.imap.server.com" IMAP_SERVER_PORT="60993" IMAP_USERNAME="johndoe" IMAP_PASSWORD="p4ssword" vendor/bin/phpunit -``` +Then the local build can be triggered with a bare `make`. -You can also copy `phpunit.xml.dist` file to a custom `phpunit.xml` and put -these environment variables in it. - -**WARNING** Tests create new mailboxes without removing them. - -``` -$ docker-compose run tests -``` +When you finish the development, stop the local IMAP server with `make stop-imap-server`. diff --git a/data/web/inc/lib/vendor/ddeboer/imap/composer.json b/data/web/inc/lib/vendor/ddeboer/imap/composer.json index 0bcf72af4..357c01972 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/composer.json +++ b/data/web/inc/lib/vendor/ddeboer/imap/composer.json @@ -22,19 +22,20 @@ } ], "require": { - "php": "^8.0.1", + "php": "~8.2.0 || ~8.3.0", + "ext-dom": "*", "ext-iconv": "*", "ext-imap": "*", + "ext-libxml": "*", "ext-mbstring": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.4.0", - "laminas/laminas-mail": "^2.15.1", - "malukenho/mcbumpface": "^1.1.5", - "phpstan/phpstan": "^1.3.3", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpunit/phpunit": "^9.5.11" + "friendsofphp/php-cs-fixer": "^3.38.2", + "laminas/laminas-mail": "^2.25.1", + "phpstan/phpstan": "^1.10.43", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "phpunit/phpunit": "^10.4.2" }, "autoload": { "psr-4": { @@ -45,10 +46,5 @@ "psr-4": { "Ddeboer\\Imap\\Tests\\": "tests/" } - }, - "config": { - "allow-plugins": { - "malukenho/mcbumpface": true - } } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php index 9f5ef90ff..538080e90 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php @@ -50,9 +50,11 @@ final class Connection implements ConnectionInterface public function close(int $flag = 0): bool { + $stream = $this->resource->getStream(); + $this->resource->clearLastMailboxUsedCache(); - return \imap_close($this->resource->getStream(), $flag); + return \imap_close($stream, $flag); } public function getQuota(string $root = 'INBOX'): array @@ -61,7 +63,7 @@ final class Connection implements ConnectionInterface $errorNumber = 0; \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { $errorMessage = $message; - $errorNumber = $nr; + $errorNumber = $nr; return true; }); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php index 6289339a7..5d27854cb 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php @@ -6,7 +6,6 @@ namespace Ddeboer\Imap; use Ddeboer\Imap\Exception\CreateMailboxException; use Ddeboer\Imap\Exception\DeleteMailboxException; -use Ddeboer\Imap\Exception\InvalidResourceException; use Ddeboer\Imap\Exception\MailboxDoesNotExistException; /** @@ -31,8 +30,6 @@ interface ConnectionInterface extends \Countable /** * Check if the connection is still active. - * - * @throws InvalidResourceException If connection was closed */ public function ping(): bool; diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidResourceException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/RenameMailboxException.php old mode 100644 new mode 100755 similarity index 53% rename from data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidResourceException.php rename to data/web/inc/lib/vendor/ddeboer/imap/src/Exception/RenameMailboxException.php index f3cbf5b69..2b221e77a --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/InvalidResourceException.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/RenameMailboxException.php @@ -4,6 +4,6 @@ declare(strict_types=1); namespace Ddeboer\Imap\Exception; -final class InvalidResourceException extends AbstractException +final class RenameMailboxException extends AbstractException { } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php index 60d348a9e..b488f0533 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResource.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Ddeboer\Imap; -use Ddeboer\Imap\Exception\InvalidResourceException; use Ddeboer\Imap\Exception\ReopenMailboxException; use IMAP\Connection; @@ -13,33 +12,21 @@ use IMAP\Connection; */ final class ImapResource implements ImapResourceInterface { - /** - * @var resource - */ - private $resource; - private ?MailboxInterface $mailbox = null; + private Connection $resource; + private ?MailboxInterface $mailbox; private static ?string $lastMailboxUsedCache = null; /** * Constructor. - * - * @param Connection|resource $resource */ - public function __construct($resource, MailboxInterface $mailbox = null) + public function __construct(Connection $resource, MailboxInterface $mailbox = null) { $this->resource = $resource; $this->mailbox = $mailbox; } - public function getStream() + public function getStream(): Connection { - if ( - !$this->resource instanceof Connection - && (false === \is_resource($this->resource) || 'imap' !== \get_resource_type($this->resource)) - ) { - throw new InvalidResourceException('Supplied resource is not a valid imap resource'); - } - $this->initMailbox(); return $this->resource; @@ -76,10 +63,8 @@ final class ImapResource implements ImapResourceInterface /** * Check whether the current mailbox is open. - * - * @param resource $resource */ - private static function isMailboxOpen(MailboxInterface $mailbox, $resource): bool + private static function isMailboxOpen(MailboxInterface $mailbox, Connection $resource): bool { $currentMailboxName = $mailbox->getFullEncodedName(); if ($currentMailboxName === self::$lastMailboxUsedCache) { diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php index cf49f6b7c..ff3c15cde 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php @@ -4,18 +4,14 @@ declare(strict_types=1); namespace Ddeboer\Imap; -use Ddeboer\Imap\Exception\InvalidResourceException; +use IMAP\Connection; interface ImapResourceInterface { /** * Get IMAP resource stream. - * - * @throws InvalidResourceException - * - * @return resource */ - public function getStream(); + public function getStream(): Connection; /** * Clear last mailbox used cache. diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php index 38823fee2..f1df2ad84 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Mailbox.php @@ -4,12 +4,12 @@ declare(strict_types=1); namespace Ddeboer\Imap; -use DateTimeInterface; use Ddeboer\Imap\Exception\ImapNumMsgException; use Ddeboer\Imap\Exception\ImapStatusException; use Ddeboer\Imap\Exception\InvalidSearchCriteriaException; use Ddeboer\Imap\Exception\MessageCopyException; use Ddeboer\Imap\Exception\MessageMoveException; +use Ddeboer\Imap\Exception\RenameMailboxException; use Ddeboer\Imap\Search\ConditionInterface; use Ddeboer\Imap\Search\LogicalOperator\All; @@ -41,6 +41,23 @@ final class Mailbox implements MailboxInterface return $this->name; } + public function renameTo(string $name): bool + { + $encodedName = \mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'); + $oldFullName = $this->getFullEncodedName(); + $newFullName = \preg_replace('/' . \preg_quote(\mb_convert_encoding($this->name, 'UTF7-IMAP', 'UTF-8')) . '$/', $encodedName, $oldFullName); + \assert(null !== $newFullName); + + $return = \imap_renamemailbox($this->resource->getStream(), $oldFullName, $newFullName); + if (false === $return) { + throw new RenameMailboxException('Could not rename mailbox'); + } + $this->name = $name; + $this->info->name = $newFullName; + + return true; + } + public function getEncodedName(): string { /** @var string $name */ @@ -104,10 +121,6 @@ final class Mailbox implements MailboxInterface } $query = $search->toString(); - if (\PHP_VERSION_ID < 80000) { - $descending = (int) $descending; - } - // We need to clear the stack to know whether imap_last_error() // is related to this imap_search \imap_errors(); @@ -178,7 +191,7 @@ final class Mailbox implements MailboxInterface return $this->getMessages(); } - public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool + public function addMessage(string $message, string $options = null, \DateTimeInterface $internalDate = null): bool { $arguments = [ $this->resource->getStream(), diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php index 067bdc288..f32480495 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Ddeboer\Imap; -use DateTimeInterface; use Ddeboer\Imap\Message\PartInterface; use Ddeboer\Imap\Search\ConditionInterface; @@ -20,6 +19,11 @@ interface MailboxInterface extends \Countable, \IteratorAggregate */ public function getName(): string; + /** + * Set new mailbox name. + */ + public function renameTo(string $name): bool; + /** * Get mailbox encoded path. */ @@ -92,7 +96,7 @@ interface MailboxInterface extends \Countable, \IteratorAggregate /** * Add a message to the mailbox. */ - public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool; + public function addMessage(string $message, string $options = null, \DateTimeInterface $internalDate = null): bool; /** * Returns a tree of threaded message for the current Mailbox. diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php index 1a9d16e73..c176e5f87 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message.php @@ -49,7 +49,7 @@ final class Message extends Message\AbstractMessage implements MessageInterface $errorNumber = 0; \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { $errorMessage = $message; - $errorNumber = $nr; + $errorNumber = $nr; return true; }); @@ -80,7 +80,6 @@ final class Message extends Message\AbstractMessage implements MessageInterface } $this->messageNumberVerified = true; - $msgno = null; \set_error_handler(static function (): bool { return true; }); @@ -89,7 +88,7 @@ final class Message extends Message\AbstractMessage implements MessageInterface \restore_error_handler(); - if (\is_numeric($msgno) && $msgno > 0) { + if ($msgno > 0) { $this->imapMsgNo = $msgno; return; @@ -133,6 +132,14 @@ final class Message extends Message\AbstractMessage implements MessageInterface return $this->rawMessage; } + /** + * @param resource|string $file the path to the saved file as a string, or a valid file descriptor + */ + public function saveRawMessage($file): void + { + $this->doSaveContent($file, ''); + } + public function getHeaders(): Message\Headers { if (null === $this->headers) { @@ -198,8 +205,6 @@ final class Message extends Message\AbstractMessage implements MessageInterface public function maskAsSeen(): bool { - \trigger_error(\sprintf('%s is deprecated and will be removed in 2.0. Use %s::markAsSeen instead.', __METHOD__, __CLASS__), \E_USER_DEPRECATED); - return $this->markAsSeen(); } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php index 0f022cc47..80ea23839 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php @@ -203,23 +203,86 @@ abstract class AbstractMessage extends AbstractPart } /** - * Get body HTML. + * Get first body HTML part. */ final public function getBodyHtml(): ?string { - $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + $htmlParts = $this->getAllContentsBySubtype(self::SUBTYPE_HTML); + + return $htmlParts[0] ?? null; + } + + /** + * Get all contents parts of specific subtype (self::SUBTYPE_HTML or self::SUBTYPE_PLAIN). + * + * @return string[] + */ + final public function getAllContentsBySubtype(string $subtype): array + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + $parts = []; + /** @var PartInterface $part */ foreach ($iterator as $part) { - if (self::SUBTYPE_HTML === $part->getSubtype()) { - return $part->getDecodedContent(); + if ($subtype === $part->getSubtype()) { + $parts[] = $part->getDecodedContent(); + } + } + if (\count($parts) > 0) { + return $parts; + } + + // If message has no parts and is of right type, return content of message. + if ($subtype === $this->getSubtype()) { + return [$this->getDecodedContent()]; + } + + return []; + } + + /** + * Get body HTML parts. + * + * @return string[] + */ + final public function getBodyHtmlParts(): array + { + return $this->getAllContentsBySubtype(self::SUBTYPE_HTML); + } + + /** + * Get all body HTML parts merged into 1 html. + */ + final public function getCompleteBodyHtml(): ?string + { + $htmlParts = $this->getAllContentsBySubtype(self::SUBTYPE_HTML); + + if (1 === \count($htmlParts)) { + return $htmlParts[0]; + } + if (0 === \count($htmlParts)) { + return null; + } + \libxml_use_internal_errors(true); // Suppress parse errors, get errors with libxml_get_errors(); + + $newDom = new \DOMDocument(); + + $newBody = ''; + $newDom->loadHTML(\implode('', $htmlParts)); + + $bodyTags = $newDom->getElementsByTagName('body'); + + foreach ($bodyTags as $body) { + foreach ($body->childNodes as $node) { + $newBody .= $newDom->saveHTML($node); } } - // If message has no parts and is HTML, return content of message itself. - if (self::SUBTYPE_HTML === $this->getSubtype()) { - return $this->getDecodedContent(); - } + $newDom = new \DOMDocument(); + $newDom->loadHTML($newBody); - return null; + $completeHtml = $newDom->saveHTML(); + + return false === $completeHtml ? null : $completeHtml; } /** @@ -227,19 +290,28 @@ abstract class AbstractMessage extends AbstractPart */ final public function getBodyText(): ?string { - $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); - foreach ($iterator as $part) { - if (self::SUBTYPE_PLAIN === $part->getSubtype()) { - return $part->getDecodedContent(); - } + $plainParts = $this->getAllContentsBySubtype(self::SUBTYPE_PLAIN); + + return $plainParts[0] ?? null; + } + + /** + * Get all body PLAIN parts merged into 1 string. + * + * @return null|string Null if message has no PLAIN message parts + */ + final public function getCompleteBodyText(): ?string + { + $plainParts = $this->getAllContentsBySubtype(self::SUBTYPE_PLAIN); + + if (1 === \count($plainParts)) { + return $plainParts[0]; + } + if (0 === \count($plainParts)) { + return null; } - // If message has no parts, return content of message itself. - if (self::SUBTYPE_PLAIN === $this->getSubtype()) { - return $this->getDecodedContent(); - } - - return null; + return \implode("\n", $plainParts); } /** @@ -269,7 +341,7 @@ abstract class AbstractMessage extends AbstractPart $attachments[] = $childPart; } if ($childPart->hasChildren()) { - $attachments = \array_merge($attachments, self::gatherAttachments($childPart)); + $attachments = [...$attachments, ...self::gatherAttachments($childPart)]; } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php index 0647133c5..3adba507b 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php @@ -56,8 +56,7 @@ abstract class AbstractPart implements PartInterface private ?string $encoding = null; private ?string $disposition = null; private ?string $description = null; - /** @var null|int|string */ - private $bytes; + private string|int|null $bytes; private ?string $lines = null; private ?string $content = null; private ?string $decodedContent = null; @@ -256,6 +255,26 @@ abstract class AbstractPart implements PartInterface return $return; } + /** + * Save raw message content to file. + * + * @param resource|string $file the path to the saved file as a string, or a valid file descriptor + */ + final protected function doSaveContent($file, string $partNumber): void + { + $return = \imap_savebody( + $this->resource->getStream(), + $file, + $this->getNumber(), + $partNumber, + \FT_UID | \FT_PEEK + ); + + if (false === $return) { + throw new ImapFetchbodyException('imap_savebody failed'); + } + } + final public function getParts(): array { $this->lazyParseStructure(); @@ -265,8 +284,6 @@ abstract class AbstractPart implements PartInterface /** * Get current child part. - * - * @return mixed */ #[\ReturnTypeWillChange] final public function current() @@ -360,7 +377,7 @@ abstract class AbstractPart implements PartInterface // When the message is not multipart and the body is the attachment content // Prevents infinite recursion - if (self::isAttachment($this->structure) && !$this instanceof Attachment) { + if (!$this instanceof Attachment && self::isAttachment($this->structure)) { $this->parts[] = new Attachment($this->resource, $this->getNumber(), '1', $this->structure); } @@ -372,12 +389,11 @@ abstract class AbstractPart implements PartInterface } foreach ($parts as $key => $partStructure) { $partNumber = (!$this instanceof Message) ? $this->partNumber . '.' : ''; - $partNumber .= (string) ($key + 1); + $partNumber .= $key + 1; $newPartClass = self::isAttachment($partStructure) ? Attachment::class - : SimplePart::class - ; + : SimplePart::class; $this->parts[] = new $newPartClass($this->resource, $this->getNumber(), $partNumber, $partStructure); } @@ -400,9 +416,9 @@ abstract class AbstractPart implements PartInterface } if ( - 'inline' === \strtolower($part->disposition) + 'inline' === \strtolower($part->disposition) && self::SUBTYPE_PLAIN !== \strtoupper($part->subtype) - && self::SUBTYPE_HTML !== \strtoupper($part->subtype) + && self::SUBTYPE_HTML !== \strtoupper($part->subtype) ) { return true; } @@ -427,10 +443,6 @@ abstract class AbstractPart implements PartInterface } */ - if (self::SUBTYPE_RFC822 === \strtoupper($part->subtype)) { - return true; - } - - return false; + return self::SUBTYPE_RFC822 === \strtoupper($part->subtype); } } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php index 83dbd17f8..95d3f26d6 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php @@ -18,6 +18,13 @@ interface BasicMessageInterface extends PartInterface */ public function getRawMessage(): string; + /** + * Save the raw message, including all headers, parts, etc. unencoded and unparsed to file. + * + * @param resource|string $file the path to the saved file as a string, or a valid file descriptor + */ + public function saveRawMessage($file): void; + /** * Get message headers. */ @@ -109,17 +116,45 @@ interface BasicMessageInterface extends PartInterface public function getReferences(): array; /** - * Get body HTML. + * Get message parts by type. + * + * @return string[] + */ + public function getAllContentsBySubtype(string $subtype): array; + + /** + * Get first body HTML part. * * @return null|string Null if message has no HTML message part */ public function getBodyHtml(): ?string; + /** + * Get all body HTML parts as array. + * + * @return string[] + */ + public function getBodyHtmlParts(): array; + + /** + * Get all body HTML parts merged into 1 html. + * + * @return null|string Null if message has no HTML message parts + */ + public function getCompleteBodyHtml(): ?string; + /** * Get body text. */ public function getBodyText(): ?string; + /** + * Get all body PLAIN parts merged into 1 string. + * + * @return null|string Null if message has no PLAIN message parts + */ + public function getCompleteBodyText(): ?string; + /** * Get attachments (if any) linked to this e-mail. * diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php index 20d9715f2..80f542b2d 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php @@ -38,6 +38,14 @@ final class EmbeddedMessage extends AbstractMessage implements EmbeddedMessageIn return $this->rawMessage; } + /** + * @param resource|string $file the path to the saved file as a string, or a valid file descriptor + */ + public function saveRawMessage($file): void + { + $this->doSaveContent($file, $this->getPartNumber()); + } + /** * Get content part number. */ diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php index 0dc63526b..07a675a88 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Ddeboer\Imap\Message; +use Ddeboer\Imap\Exception\UnsupportedCharsetException; + final class Headers extends Parameters { public function __construct(\stdClass $headers) @@ -14,7 +16,11 @@ final class Headers extends Parameters $headers = \array_change_key_case((array) $headers); foreach ($headers as $key => $value) { - $this[$key] = $this->parseHeader($key, $value); + try { + $this[$key] = $this->parseHeader($key, $value); + } catch (UnsupportedCharsetException) { + // safely skip header with unsupported charset + } } } @@ -50,7 +56,6 @@ final class Headers extends Parameters case 'sender': case 'return_path': \assert(\is_array($value)); - /** @var \stdClass $address */ foreach ($value as $address) { if (isset($address->mailbox)) { $address->host = $address->host ?? null; diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php index bd439e068..91b297eb5 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php @@ -304,7 +304,7 @@ final class Transcoder $errorNumber = 0; \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { $errorMessage = $message; - $errorNumber = $nr; + $errorNumber = $nr; return true; }); diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php index 48d87ee3a..2b1ee6337 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIterator.php @@ -39,8 +39,8 @@ final class MessageIterator extends \ArrayIterator implements MessageIteratorInt 'The current value "%s" isn\'t an integer and doesn\'t represent a message;' . ' try to cycle this "%s" with a native php function like foreach or with the method getArrayCopy(),' . ' or check it by calling the methods valid().', - \is_object($current) ? \get_class($current) : \gettype($current), - static::class + \get_debug_type($current), + self::class )); } diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php index 61bd05e7d..58dbff232 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractDate.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace Ddeboer\Imap\Search; -use DateTimeInterface; - /** * Represents a date condition. */ @@ -19,14 +17,14 @@ abstract class AbstractDate implements ConditionInterface /** * The date to be used for the condition. */ - private DateTimeInterface $date; + private \DateTimeInterface $date; /** * Constructor. * - * @param DateTimeInterface $date optional date for the condition + * @param \DateTimeInterface $date optional date for the condition */ - public function __construct(DateTimeInterface $date, string $dateFormat = 'j-M-Y') + public function __construct(\DateTimeInterface $date, string $dateFormat = 'j-M-Y') { $this->date = $date; $this->dateFormat = $dateFormat; diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php index 41a953d9f..8a5a1b8f8 100644 --- a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php @@ -67,7 +67,7 @@ final class Server implements ServerInterface $errorNumber = 0; \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool { $errorMessage = $message; - $errorNumber = $nr; + $errorNumber = $nr; return true; }); diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json b/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json index 97e84cd5e..85d39f22c 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/composer.json @@ -36,8 +36,8 @@ "psr/log": "*", "nesbot/carbon": "*", "psr/simple-cache": "^1.0|^2.0|^3.0", - "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0" + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/collections": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0" }, "require-dev": { "phpunit/phpunit": "^9.0", diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php index d560098b7..db2fd5775 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Auth/Guard.php @@ -3,7 +3,9 @@ namespace LdapRecord\Auth; use Exception; +use LdapRecord\Configuration\ConfigurationException; use LdapRecord\Configuration\DomainConfiguration; +use LdapRecord\ConnectionException; use LdapRecord\Events\DispatcherInterface; use LdapRecord\LdapInterface; @@ -71,7 +73,7 @@ class Guard * Attempt binding a user to the LDAP server. Supports sasl and anonymous binding. * * @throws BindException - * @throws \LdapRecord\ConnectionException + * @throws ConnectionException */ public function bind(?string $username = null, ?string $password = null): void { @@ -102,7 +104,7 @@ class Guard /** * Authenticate by binding to the LDAP server. * - * @throws \LdapRecord\ConnectionException + * @throws ConnectionException */ protected function authenticate(?string $username = null, ?string $password = null): bool { @@ -119,8 +121,8 @@ class Guard * Bind to the LDAP server using the configured username and password. * * @throws BindException - * @throws \LdapRecord\ConnectionException - * @throws \LdapRecord\Configuration\ConfigurationException + * @throws ConnectionException + * @throws ConfigurationException */ public function bindAsConfiguredUser(): void { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php index e04393a27..45fd66dc3 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php @@ -89,7 +89,7 @@ class Connection /** * Set the connection configuration. * - * @throws Configuration\ConfigurationException + * @throws ConfigurationException */ public function setConfiguration(DomainConfiguration|array $config = []): void { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php index 7f2be60b6..1fa632860 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Ldap.php @@ -3,6 +3,8 @@ namespace LdapRecord; use LDAP\Connection as RawLdapConnection; +use Ldap\Result; +use Ldap\ResultEntry; class Ldap implements LdapInterface { @@ -24,7 +26,7 @@ class Ldap implements LdapInterface * * @see http://php.net/manual/en/function.ldap-first-entry.php * - * @param \Ldap\Result $result + * @param Result $result */ public function getFirstEntry(mixed $result): mixed { @@ -38,7 +40,7 @@ class Ldap implements LdapInterface * * @see http://php.net/manual/en/function.ldap-next-entry.php * - * @param \Ldap\ResultEntry $entry + * @param ResultEntry $entry */ public function getNextEntry(mixed $entry): mixed { @@ -52,7 +54,7 @@ class Ldap implements LdapInterface * * @see http://php.net/manual/en/function.ldap-get-attributes.php * - * @param \Ldap\ResultEntry $entry + * @param ResultEntry $entry */ public function getAttributes(mixed $entry): array|false { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php index 735f858ca..2130e08ed 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/LdapInterface.php @@ -3,6 +3,7 @@ namespace LdapRecord; use LDAP\Connection; +use LDAP\Result; /** * @see https://ldap.com/ldap-oid-reference-guide @@ -339,7 +340,7 @@ interface LdapInterface * * @see http://php.net/manual/en/function.ldap-get-entries.php * - * @param \LDAP\Result $result + * @param Result $result */ public function getEntries(mixed $result): array; @@ -348,7 +349,7 @@ interface LdapInterface * * @see https://www.php.net/manual/en/function.ldap-first-entry.php * - * @param \LDAP\Result $result + * @param Result $result */ public function getFirstEntry(mixed $result): mixed; @@ -357,7 +358,7 @@ interface LdapInterface * * @see https://www.php.net/manual/en/function.ldap-next-entry.php * - * @param \LDAP\Result $entry + * @param Result $entry */ public function getNextEntry(mixed $entry): mixed; @@ -366,14 +367,14 @@ interface LdapInterface * * @see https://www.php.net/manual/en/function.ldap-get-attributes.php * - * @param \LDAP\Result $entry + * @param Result $entry */ public function getAttributes(mixed $entry): array|false; /** * Reads all the values of the attribute in the entry in the result. * - * @param \LDAP\Result $entry + * @param Result $entry */ public function getValuesLen(mixed $entry, string $attribute): array|false; @@ -398,7 +399,7 @@ interface LdapInterface * * @see https://www.php.net/manual/en/function.ldap-count-entries.php * - * @param \LDAP\Result $result + * @param Result $result */ public function countEntries(mixed $result): int; @@ -463,7 +464,7 @@ interface LdapInterface * * @see http://php.net/manual/en/function.ldap-search.php * - * @return \LDAP\Result + * @return Result */ public function search(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed; @@ -472,7 +473,7 @@ interface LdapInterface * * @see http://php.net/manual/en/function.ldap-list.php * - * @return \LDAP\Result + * @return Result */ public function list(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed; @@ -481,7 +482,7 @@ interface LdapInterface * * @see http://php.net/manual/en/function.ldap-read.php * - * @return \LDAP\Result + * @return Result */ public function read(string $dn, string $filter, array $fields, bool $onlyAttributes = false, int $size = 0, int $time = 0, int $deref = LDAP_DEREF_NEVER, ?array $controls = null): mixed; @@ -490,7 +491,7 @@ interface LdapInterface * * @see https://www.php.net/manual/en/function.ldap-parse-result.php * - * @param \LDAP\Result $result + * @param Result $result */ public function parseResult(mixed $result, int &$errorCode = 0, ?string &$dn = null, ?string &$errorMessage = null, ?array &$referrals = null, ?array &$controls = null): LdapResultResponse|false; @@ -595,7 +596,7 @@ interface LdapInterface * * @see https://www.php.net/manual/en/function.ldap-free-result.php * - * @param \LDAP\Result $result + * @param Result $result */ public function freeResult(mixed $result): bool; diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php index e9924f433..2228b45c2 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Entry.php @@ -4,6 +4,7 @@ namespace LdapRecord\Models\ActiveDirectory; use InvalidArgumentException; use LdapRecord\Connection; +use LdapRecord\LdapRecordException; use LdapRecord\Models\Attributes\Sid; use LdapRecord\Models\Entry as BaseEntry; use LdapRecord\Models\Events\Updated; @@ -99,7 +100,7 @@ class Entry extends BaseEntry implements ActiveDirectory /** * Restore a deleted object. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function restore(?string $newParentDn = null): bool { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php index b7942cf4d..5aa4ca018 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/ActiveDirectory/Scopes/InConfigurationContext.php @@ -4,6 +4,7 @@ namespace LdapRecord\Models\ActiveDirectory\Scopes; use LdapRecord\Models\ActiveDirectory\Entry; use LdapRecord\Models\Model; +use LdapRecord\Models\ModelNotFoundException; use LdapRecord\Models\Scope; use LdapRecord\Query\Model\Builder; @@ -12,7 +13,7 @@ class InConfigurationContext implements Scope /** * Refines the base dn to be inside the configuration context. * - * @throws \LdapRecord\Models\ModelNotFoundException + * @throws ModelNotFoundException */ public function apply(Builder $query, Model $model): void { @@ -22,7 +23,7 @@ class InConfigurationContext implements Scope /** * Get the LDAP server configuration naming context distinguished name. * - * @throws \LdapRecord\Models\ModelNotFoundException + * @throws ModelNotFoundException */ protected function getConfigurationNamingContext(Model $model) { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php index e52657a44..95be4e8cf 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/CanAuthenticate.php @@ -2,7 +2,9 @@ namespace LdapRecord\Models\Concerns; -/** @mixin \LdapRecord\Models\Model */ +use LdapRecord\Models\Model; + +/** @mixin Model */ trait CanAuthenticate { /** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php index 7537f62f6..2ebd33284 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasEvents.php @@ -6,9 +6,10 @@ use Closure; use LdapRecord\Events\NullDispatcher; use LdapRecord\Models\Events; use LdapRecord\Models\Events\Event; +use LdapRecord\Models\Model; use LdapRecord\Support\Arr; -/** @mixin \LdapRecord\Models\Model */ +/** @mixin Model */ trait HasEvents { /** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php index 855ba37c3..9883d55dd 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasGlobalScopes.php @@ -4,9 +4,10 @@ namespace LdapRecord\Models\Concerns; use Closure; use InvalidArgumentException; +use LdapRecord\Models\Model; use LdapRecord\Models\Scope; -/** @mixin \LdapRecord\Models\Model */ +/** @mixin Model */ trait HasGlobalScopes { /** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php index c84bd977d..df4d5b00f 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasPassword.php @@ -5,8 +5,9 @@ namespace LdapRecord\Models\Concerns; use LdapRecord\ConnectionException; use LdapRecord\LdapRecordException; use LdapRecord\Models\Attributes\Password; +use LdapRecord\Models\Model; -/** @mixin \LdapRecord\Models\Model */ +/** @mixin Model */ trait HasPassword { /** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php index 7420620ce..361ec5459 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HasScopes.php @@ -2,9 +2,10 @@ namespace LdapRecord\Models\Concerns; +use LdapRecord\Models\Model; use LdapRecord\Query\Model\Builder; -/** @mixin \LdapRecord\Models\Model */ +/** @mixin Model */ trait HasScopes { /** diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php index 533e8fc58..e4e9988b9 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Concerns/HidesAttributes.php @@ -2,12 +2,14 @@ namespace LdapRecord\Models\Concerns; +use LdapRecord\Models\Model; + /** * @author Taylor Otwell * * @see https://laravel.com * - * @mixin \LdapRecord\Models\Model + * @mixin Model */ trait HidesAttributes { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php index b17d0793a..6b9c79bcf 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Model.php @@ -9,6 +9,7 @@ use JsonSerializable; use LdapRecord\Connection; use LdapRecord\Container; use LdapRecord\EscapesValues; +use LdapRecord\LdapRecordException; use LdapRecord\Models\Attributes\DistinguishedName; use LdapRecord\Models\Attributes\Guid; use LdapRecord\Query\Builder as BaseBuilder; @@ -246,7 +247,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Get the RootDSE (AD schema) record from the directory. * - * @throws \LdapRecord\Models\ModelNotFoundException + * @throws ModelNotFoundException */ public static function getRootDse(?string $connection = null): Model { @@ -919,7 +920,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Save the model to the directory without raising any events. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function saveQuietly(array $attributes = []): void { @@ -931,7 +932,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Save the model to the directory. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function save(array $attributes = []): void { @@ -951,7 +952,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Inserts the model into the directory. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ protected function performInsert(): void { @@ -998,7 +999,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Updates the model in the directory. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ protected function performUpdate(): void { @@ -1020,7 +1021,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Create the model in the directory. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public static function create(array $attributes = []): static { @@ -1035,7 +1036,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * Add an attribute on the model with the given value. * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function addAttribute(string $attribute, mixed $value): void { @@ -1054,7 +1055,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * Update the model. * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function update(array $attributes = []): void { @@ -1067,7 +1068,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * Update the model attribute with the specified value. * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function replaceAttribute(string $attribute, mixed $value): void { @@ -1085,7 +1086,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Destroy the models for the given distinguished names. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public static function destroy(mixed $dns, bool $recursive = false): int { @@ -1121,7 +1122,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * not exist or does not contain a distinguished name. * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function delete(bool $recursive = false): void { @@ -1146,7 +1147,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String /** * Deletes leaf nodes that are attached to the model. * - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ protected function deleteLeafNodes(): void { @@ -1162,7 +1163,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * Remove an attribute on the model. * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function removeAttribute(string $attribute, mixed $value = null): void { @@ -1181,7 +1182,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * ["memberuid" => []] * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function removeAttributes(array|string $attributes): void { @@ -1238,7 +1239,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * * @throws UnexpectedValueException * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function move(Model|string $newParentDn, bool $deleteOldRdn = true): void { @@ -1255,7 +1256,7 @@ abstract class Model implements Arrayable, ArrayAccess, JsonSerializable, String * Rename the model to a new RDN and new parent. * * @throws ModelDoesNotExistException - * @throws \LdapRecord\LdapRecordException + * @throws LdapRecordException */ public function rename(string $rdn, Model|string|null $newParentDn = null, bool $deleteOldRdn = true): void { diff --git a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php index 90cbf7ae6..7fbcfbab4 100644 --- a/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php +++ b/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Testing/DirectoryFake.php @@ -2,21 +2,23 @@ namespace LdapRecord\Testing; +use LdapRecord\Connection; use LdapRecord\Container; +use LdapRecord\ContainerException; class DirectoryFake { /** * The LDAP connections that were replaced with fakes. * - * @var \LdapRecord\Connection[] + * @var Connection[] */ protected static array $replaced = []; /** * Replace a connection a fake. * - * @throws \LdapRecord\ContainerException + * @throws ContainerException */ public static function setup(?string $name = null): ConnectionFake { diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md b/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md index 35e97fe86..8597266f5 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md +++ b/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md @@ -1,5 +1,160 @@ # Changelog +## [7.1.1](https://github.com/googleapis/php-jwt/compare/v7.1.0...v7.1.1) (2026-09-02) + + +### Bug Fixes + +* Limit the number of segments a JWT can be exploded into ([#639](https://github.com/googleapis/php-jwt/issues/639)) ([93d248c](https://github.com/googleapis/php-jwt/commit/93d248c650aea615d74a5ffa20c91c7a818e9b8d)) + +## [7.1.0](https://github.com/googleapis/php-jwt/compare/v7.0.5...v7.1.0) (2026-06-11) + + +### Features + +* Add support for PS256 ([#637](https://github.com/googleapis/php-jwt/issues/637)) ([aa0b447](https://github.com/googleapis/php-jwt/commit/aa0b4472c137255545b177a1840891304eac55f1)) + + +### Bug Fixes + +* Skip parsing valid unsupported curves ([#638](https://github.com/googleapis/php-jwt/issues/638)) ([8a6c2a7](https://github.com/googleapis/php-jwt/commit/8a6c2a75d7f45e781c93d618939f8a045fe69f53)) +* Update homepage URL in composer.json ([#635](https://github.com/googleapis/php-jwt/issues/635)) ([f7ae571](https://github.com/googleapis/php-jwt/commit/f7ae571efe255aed89c8f8d0f2cb7f71c5835664)) +* Validate numeric type of iat, nbf and exp claims in encode ([#634](https://github.com/googleapis/php-jwt/issues/634)) ([958e422](https://github.com/googleapis/php-jwt/commit/958e422961e874843a7fcc339e55a0101a66201d)) + +## [7.0.5](https://github.com/firebase/php-jwt/compare/v7.0.4...v7.0.5) (2026-03-31) + + +### Bug Fixes + +* RSA from JWK sometimes returns empty Instance ([#628](https://github.com/firebase/php-jwt/issues/628)) ([b4c78aa](https://github.com/firebase/php-jwt/commit/b4c78aa731664122198ad36c0033aa29e807397a)) + +## [7.0.4](https://github.com/firebase/php-jwt/compare/v7.0.3...v7.0.4) (2026-03-27) + + +### Bug Fixes + +* readme examples, add tests for all examples ([#626](https://github.com/firebase/php-jwt/issues/626)) ([510a00c](https://github.com/firebase/php-jwt/commit/510a00c0e6353bc7d68412fab67e57a13954cb46)) +* use urlsafeB64Decode everywhere ([#627](https://github.com/firebase/php-jwt/issues/627)) ([b889495](https://github.com/firebase/php-jwt/commit/b889495c83ddc3f3885ca3f0b65b41b1cb37a3b1)) + +## [7.0.3](https://github.com/firebase/php-jwt/compare/v7.0.2...v7.0.3) (2026-02-18) + + +### Miscellaneous Chores + +* add environment for Release Please job ([#619](https://github.com/firebase/php-jwt/issues/619)) ([300fd02](https://github.com/firebase/php-jwt/commit/300fd02c883f096c9067df652dbd23f62cb5e2a7)) + +## [7.0.2](https://github.com/firebase/php-jwt/compare/v7.0.1...v7.0.2) (2025-12-16) + + +### Bug Fixes + +* add key length validation for ec keys ([#615](https://github.com/firebase/php-jwt/issues/615)) ([7044f9a](https://github.com/firebase/php-jwt/commit/7044f9ae7e7d175d28cca71714feb236f1c0e252)) + +## [7.0.0](https://github.com/firebase/php-jwt/compare/v6.11.1...v7.0.0) (2025-12-15) + + +### ⚠️ ⚠️ ⚠️ Security Fixes ⚠️ ⚠️ ⚠️ + * add key size validation ([#613](https://github.com/firebase/php-jwt/issues/613)) ([6b80341](https://github.com/firebase/php-jwt/commit/6b80341bf57838ea2d011487917337901cd71576)) + **NOTE**: This fix will cause keys with a size below the minimally allowed size to break. + +### Features + +* add SensitiveParameter attribute to security-critical parameters ([#603](https://github.com/firebase/php-jwt/issues/603)) ([4dbfac0](https://github.com/firebase/php-jwt/commit/4dbfac0260eeb0e9e643063c99998e3219cc539b)) +* store timestamp in `ExpiredException` ([#604](https://github.com/firebase/php-jwt/issues/604)) ([f174826](https://github.com/firebase/php-jwt/commit/f1748260d218a856b6a0c23715ac7fae1d7ca95b)) + + +### Bug Fixes + +* validate iat and nbf on payload ([#568](https://github.com/firebase/php-jwt/issues/568)) ([953b2c8](https://github.com/firebase/php-jwt/commit/953b2c88bb445b7e3bb82a5141928f13d7343afd)) + +## [6.11.1](https://github.com/firebase/php-jwt/compare/v6.11.0...v6.11.1) (2025-04-09) + + +### Bug Fixes + +* update error text for consistency ([#528](https://github.com/firebase/php-jwt/issues/528)) ([c11113a](https://github.com/firebase/php-jwt/commit/c11113afa13265e016a669e75494b9203b8a7775)) + +## [6.11.0](https://github.com/firebase/php-jwt/compare/v6.10.2...v6.11.0) (2025-01-23) + + +### Features + +* support octet typed JWK ([#587](https://github.com/firebase/php-jwt/issues/587)) ([7cb8a26](https://github.com/firebase/php-jwt/commit/7cb8a265fa81edf2fa6ef8098f5bc5ae573c33ad)) + + +### Bug Fixes + +* refactor constructor Key to use PHP 8.0 syntax ([#577](https://github.com/firebase/php-jwt/issues/577)) ([29fa2ce](https://github.com/firebase/php-jwt/commit/29fa2ce9e0582cd397711eec1e80c05ce20fabca)) + +## [6.10.2](https://github.com/firebase/php-jwt/compare/v6.10.1...v6.10.2) (2024-11-24) + + +### Bug Fixes + +* Mitigate PHP8.4 deprecation warnings ([#570](https://github.com/firebase/php-jwt/issues/570)) ([76808fa](https://github.com/firebase/php-jwt/commit/76808fa227f3811aa5cdb3bf81233714b799a5b5)) +* support php 8.4 ([#583](https://github.com/firebase/php-jwt/issues/583)) ([e3d68b0](https://github.com/firebase/php-jwt/commit/e3d68b044421339443c74199edd020e03fb1887e)) + +## [6.10.1](https://github.com/firebase/php-jwt/compare/v6.10.0...v6.10.1) (2024-05-18) + + +### Bug Fixes + +* ensure ratelimit expiry is set every time ([#556](https://github.com/firebase/php-jwt/issues/556)) ([09cb208](https://github.com/firebase/php-jwt/commit/09cb2081c2c3bc0f61e2f2a5fbea5741f7498648)) +* ratelimit cache expiration ([#550](https://github.com/firebase/php-jwt/issues/550)) ([dda7250](https://github.com/firebase/php-jwt/commit/dda725033585ece30ff8cae8937320d7e9f18bae)) + +## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28) + + +### Features + +* allow typ header override ([#546](https://github.com/firebase/php-jwt/issues/546)) ([79cb30b](https://github.com/firebase/php-jwt/commit/79cb30b729a22931b2fbd6b53f20629a83031ba9)) + +## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04) + + +### Features + +* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2)) + +## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14) + + +### Bug Fixes + +* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e)) +* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4)) + +## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14) + + +### Features + +* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d)) + + +### Bug Fixes + +* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718)) + +## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14) + + +### Features + +* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a)) + +## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13) + + +### Features + +* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3)) + + +### Bug Fixes + +* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797)) + ## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12) diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/README.md b/data/web/inc/lib/vendor/firebase/php-jwt/README.md index f03826673..59a99d9fd 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/README.md +++ b/data/web/inc/lib/vendor/firebase/php-jwt/README.md @@ -17,22 +17,22 @@ composer require firebase/php-jwt ``` Optionally, install the `paragonie/sodium_compat` package from composer if your -php is < 7.2 or does not have libsodium installed: +php env does not have libsodium installed: ```bash composer require paragonie/sodium_compat ``` -Example -------- +## Example + ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; -$key = 'example_key'; +$key = 'example_key_of_sufficient_length'; $payload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', + 'iss' => 'example.org', + 'aud' => 'example.com', 'iat' => 1356999524, 'nbf' => 1357000000 ]; @@ -45,9 +45,13 @@ $payload = [ */ $jwt = JWT::encode($payload, $key, 'HS256'); $decoded = JWT::decode($jwt, new Key($key, 'HS256')); - print_r($decoded); +// Pass a stdClass in as the third parameter to get the decoded header values +$headers = new stdClass(); +$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers); +print_r($headers); + /* NOTE: This will now be an object instead of an associative array. To get an associative array, you will need to cast it as such: @@ -65,21 +69,22 @@ $decoded_array = (array) $decoded; JWT::$leeway = 60; // $leeway in seconds $decoded = JWT::decode($jwt, new Key($key, 'HS256')); ``` -Example encode/decode headers -------- + +## Example encode/decode headers + Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by this library. This is because without verifying the JWT, the header values could have been tampered with. Any value pulled from an unverified header should be treated as if it could be any string sent in from an -attacker. If this is something you still want to do in your application for whatever reason, it's possible to -decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT +attacker. If this is something you still want to do in your application for whatever reason, it's possible to +decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT header part: ```php use Firebase\JWT\JWT; -$key = 'example_key'; +$key = 'example_key_of_sufficient_length'; $payload = [ - 'iss' => 'http://example.org', - 'aud' => 'http://example.com', + 'iss' => 'example.org', + 'aud' => 'example.com', 'iat' => 1356999524, 'nbf' => 1357000000 ]; @@ -99,8 +104,9 @@ $decoded = json_decode(base64_decode($headersB64), true); print_r($decoded); ``` -Example with RS256 (openssl) ----------------------------- + +## Example with RS256 (openssl) + ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; @@ -168,8 +174,7 @@ $decoded_array = (array) $decoded; echo "Decode:\n" . print_r($decoded_array, true) . "\n"; ``` -Example with a passphrase -------------------------- +## Example with a passphrase ```php use Firebase\JWT\JWT; @@ -182,7 +187,7 @@ $passphrase = '[YOUR_PASSPHRASE]'; // Can be generated with "ssh-keygen -t rsa -m pem" $privateKeyFile = '/path/to/key-with-passphrase.pem'; -// Create a private key of type "resource" +/** @var OpenSSLAsymmetricKey $privateKey */ $privateKey = openssl_pkey_get_private( file_get_contents($privateKeyFile), $passphrase @@ -205,8 +210,8 @@ $decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; ``` -Example with EdDSA (libsodium and Ed25519 signature) ----------------------------- +## Example with EdDSA (libsodium and Ed25519 signature) + ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; @@ -234,21 +239,21 @@ echo "Encode:\n" . print_r($jwt, true) . "\n"; $decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; -```` +``` + +## Example with multiple keys -Example with multiple keys --------------------------- ```php use Firebase\JWT\JWT; use Firebase\JWT\Key; // Example RSA keys from previous example -// $privateKey1 = '...'; -// $publicKey1 = '...'; +// $privateRsKey = '...'; +// $publicRsKey = '...'; // Example EdDSA keys from previous example -// $privateKey2 = '...'; -// $publicKey2 = '...'; +// $privateEcKey = '...'; +// $publicEcKey = '...'; $payload = [ 'iss' => 'example.org', @@ -257,14 +262,14 @@ $payload = [ 'nbf' => 1357000000 ]; -$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1'); -$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2'); +$jwt1 = JWT::encode($payload, $privateRsKey, 'RS256', 'kid1'); +$jwt2 = JWT::encode($payload, $privateEcKey, 'EdDSA', 'kid2'); echo "Encode 1:\n" . print_r($jwt1, true) . "\n"; echo "Encode 2:\n" . print_r($jwt2, true) . "\n"; $keys = [ - 'kid1' => new Key($publicKey1, 'RS256'), - 'kid2' => new Key($publicKey2, 'EdDSA'), + 'kid1' => new Key($publicRsKey, 'RS256'), + 'kid2' => new Key($publicEcKey, 'EdDSA'), ]; $decoded1 = JWT::decode($jwt1, $keys); @@ -274,8 +279,39 @@ echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n"; echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n"; ``` -Using JWKs ----------- +## Example with PS256 + +### Note +PHP's OpenSSL extension does not support RSASSA-PSS signatures (PS256) by default, so we provide support via a soft dependency on the [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) library. It is necessary to install this library in your project if you plan to use PS256. +```bash +composer install phpseclib/phpseclib:^3.0 +``` + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$privateRsKey = '-----BEGIN RSA PRIVATE KEY----- ...'; +$publicKey = '-----BEGIN PUBLIC KEY----- ...'; + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +/** +* PS256 support requires phpseclib/phpseclib +*/ +$jwt = JWT::encode($payload, $privateRsKey, 'PS256', 'keyid'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'PS256')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +``` + +## Using JWKs ```php use Firebase\JWT\JWK; @@ -287,11 +323,11 @@ $jwks = ['keys' => []]; // JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key // objects. Pass this as the second parameter to JWT::decode. -JWT::decode($payload, JWK::parseKeySet($jwks)); +$decoded = JWT::decode($jwt, JWK::parseKeySet($jwks)); +print_r($decoded); ``` -Using Cached Key Sets ---------------------- +## Using Cached Key Sets The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI. This has the following advantages: @@ -311,7 +347,7 @@ $jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; $httpClient = new GuzzleHttp\Client(); // Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) -$httpFactory = new GuzzleHttp\Psr\HttpFactory(); +$httpFactory = new GuzzleHttp\Psr7\HttpFactory(); // Create a cache item pool (can be any PSR-6 compatible cache item pool) $cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); @@ -346,7 +382,7 @@ use InvalidArgumentException; use UnexpectedValueException; try { - $decoded = JWT::decode($payload, $keys); + $decoded = JWT::decode($jwt, $keys); } catch (InvalidArgumentException $e) { // provided key/key-array is empty or malformed. } catch (DomainException $e) { @@ -373,8 +409,10 @@ All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException` like this: ```php +use Firebase\JWT\JWT; +use UnexpectedValueException; try { - $decoded = JWT::decode($payload, $keys); + $decoded = JWT::decode($jwt, $keys); } catch (LogicException $e) { // errors having to do with environmental setup or malformed JWT Keys } catch (UnexpectedValueException $e) { @@ -389,7 +427,7 @@ instead, you can do the following: ```php // return type is stdClass -$decoded = JWT::decode($payload, $keys); +$decoded = JWT::decode($jwt, $keys); // cast to array $decoded = json_decode(json_encode($decoded), true); @@ -400,8 +438,8 @@ Tests Run the tests using phpunit: ```bash -$ pear install PHPUnit -$ phpunit --configuration phpunit.xml.dist +$ composer update +$ vendor/bin/phpunit -c phpunit.xml.dist PHPUnit 3.7.10 by Sebastian Bergmann. ..... Time: 0 seconds, Memory: 2.50Mb diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/VERSION b/data/web/inc/lib/vendor/firebase/php-jwt/VERSION new file mode 100644 index 000000000..b26a34e47 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/VERSION @@ -0,0 +1 @@ +7.2.1 diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/composer.json b/data/web/inc/lib/vendor/firebase/php-jwt/composer.json index e23dfe378..89c8c7019 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/composer.json +++ b/data/web/inc/lib/vendor/firebase/php-jwt/composer.json @@ -1,7 +1,7 @@ { "name": "firebase/php-jwt", "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", + "homepage": "https://github.com/googleapis/php-jwt", "keywords": [ "php", "jwt" @@ -20,11 +20,12 @@ ], "license": "BSD-3-Clause", "require": { - "php": "^7.4||^8.0" + "php": "^8.0" }, "suggest": { "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", - "ext-sodium": "Support EdDSA (Ed25519) signatures" + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" }, "autoload": { "psr-4": { @@ -32,11 +33,21 @@ } }, "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", - "phpspec/prophecy-phpunit": "^2.0", + "guzzlehttp/guzzle": "^7.4||^8.0", + "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0||^2.0", + "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" + "psr/http-factory": "^1.0", + "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0" + }, + "extra": { + "component": { + "id": "jwt", + "target": "googleapis/php-jwt.git", + "path": "Jwt", + "entry": "README.md" + } } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php index c147852b9..595164bf3 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/BeforeValidException.php @@ -2,6 +2,17 @@ namespace Firebase\JWT; -class BeforeValidException extends \UnexpectedValueException +class BeforeValidException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface { + private object $payload; + + public function setPayload(object $payload): void + { + $this->payload = $payload; + } + + public function getPayload(): object + { + return $this->payload; + } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php index baf801f13..37c3f94d2 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php @@ -80,9 +80,9 @@ class CachedKeySet implements ArrayAccess ClientInterface $httpClient, RequestFactoryInterface $httpFactory, CacheItemPoolInterface $cache, - int $expiresAfter = null, + ?int $expiresAfter = null, bool $rateLimit = false, - string $defaultAlg = null + ?string $defaultAlg = null ) { $this->jwksUri = $jwksUri; $this->httpClient = $httpClient; @@ -178,6 +178,17 @@ class CachedKeySet implements ArrayAccess } $request = $this->httpFactory->createRequest('GET', $this->jwksUri); $jwksResponse = $this->httpClient->sendRequest($request); + if ($jwksResponse->getStatusCode() !== 200) { + throw new UnexpectedValueException( + \sprintf( + 'HTTP Error: %d %s for URI "%s"', + $jwksResponse->getStatusCode(), + $jwksResponse->getReasonPhrase(), + $this->jwksUri, + ), + $jwksResponse->getStatusCode() + ); + } $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody()); if (!isset($this->keySet[$keyId])) { @@ -202,15 +213,21 @@ class CachedKeySet implements ArrayAccess } $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); - if (!$cacheItem->isHit()) { - $cacheItem->expiresAfter(1); // # of calls are cached each minute + + $cacheItemData = []; + if ($cacheItem->isHit() && \is_array($data = $cacheItem->get())) { + $cacheItemData = $data; } - $callsPerMinute = (int) $cacheItem->get(); + $callsPerMinute = $cacheItemData['callsPerMinute'] ?? 0; + $expiry = $cacheItemData['expiry'] ?? new \DateTime('+60 seconds', new \DateTimeZone('UTC')); + if (++$callsPerMinute > $this->maxCallsPerMinute) { return true; } - $cacheItem->set($callsPerMinute); + + $cacheItem->set(['expiry' => $expiry, 'callsPerMinute' => $callsPerMinute]); + $cacheItem->expiresAt($expiry); $this->cache->save($cacheItem); return false; } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php index 81ba52d43..25f445132 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/ExpiredException.php @@ -2,6 +2,29 @@ namespace Firebase\JWT; -class ExpiredException extends \UnexpectedValueException +class ExpiredException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface { + private object $payload; + + private ?int $timestamp = null; + + public function setPayload(object $payload): void + { + $this->payload = $payload; + } + + public function getPayload(): object + { + return $this->payload; + } + + public function setTimestamp(int $timestamp): void + { + $this->timestamp = $timestamp; + } + + public function getTimestamp(): ?int + { + return $this->timestamp; + } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php index c7eff8ae4..1870c6fe0 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php @@ -27,10 +27,25 @@ class JWK private const EC_CURVES = [ 'P-256' => '1.2.840.10045.3.1.7', // Len: 64 'secp256k1' => '1.3.132.0.10', // Len: 64 - // 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported) + 'P-384' => '1.3.132.0.34', // Len: 96 // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported) ]; + // Known standard curves from the IANA JOSE registry which are not supported + private const KNOWN_UNSUPPORTED_EC_CURVES = [ + 'P-521', // RFC 7518 + 'Ed25519', // RFC 8037 + 'Ed448', // RFC 8037 + 'X25519', // RFC 8037 + 'X448' // RFC 8037 + ]; + + // For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype. + // This library supports the following subtypes: + private const OKP_SUBTYPES = [ + 'Ed25519' => true, // RFC 8037 + ]; + /** * Parse a set of JWK keys * @@ -46,7 +61,7 @@ class JWK * * @uses parseKey */ - public static function parseKeySet(array $jwks, string $defaultAlg = null): array + public static function parseKeySet(#[\SensitiveParameter] array $jwks, ?string $defaultAlg = null): array { $keys = []; @@ -87,7 +102,7 @@ class JWK * * @uses createPemFromModulusAndExponent */ - public static function parseKey(array $jwk, string $defaultAlg = null): ?Key + public static function parseKey(#[\SensitiveParameter] array $jwk, ?string $defaultAlg = null): ?Key { if (empty($jwk)) { throw new InvalidArgumentException('JWK must not be empty'); @@ -136,7 +151,10 @@ class JWK } if (!isset(self::EC_CURVES[$jwk['crv']])) { - throw new DomainException('Unrecognised or unsupported EC curve'); + if (!\in_array($jwk['crv'], self::KNOWN_UNSUPPORTED_EC_CURVES)) { + throw new DomainException('Unrecognised EC curve'); + } + return null; } if (empty($jwk['x']) || empty($jwk['y'])) { @@ -145,8 +163,34 @@ class JWK $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); return new Key($publicKey, $jwk['alg']); + case 'OKP': + if (isset($jwk['d'])) { + // The key is actually a private key + throw new UnexpectedValueException('Key data must be for a public key'); + } + + if (!isset($jwk['crv'])) { + throw new UnexpectedValueException('crv not set'); + } + + if (empty(self::OKP_SUBTYPES[$jwk['crv']])) { + throw new DomainException('Unrecognised or unsupported OKP key subtype'); + } + + if (empty($jwk['x'])) { + throw new UnexpectedValueException('x not set'); + } + + // This library works internally with EdDSA keys (Ed25519) encoded in standard base64. + $publicKey = JWT::convertBase64urlToBase64($jwk['x']); + return new Key($publicKey, $jwk['alg']); + case 'oct': + if (!isset($jwk['k'])) { + throw new UnexpectedValueException('k not set'); + } + + return new Key(JWT::urlsafeB64Decode($jwk['k']), $jwk['alg']); default: - // Currently only RSA is supported break; } @@ -156,7 +200,7 @@ class JWK /** * Converts the EC JWK values to pem format. * - * @param string $crv The EC curve (only P-256 is supported) + * @param string $crv The EC curve (only P-256 & P-384 is supported) * @param string $x The EC x-coordinate * @param string $y The EC y-coordinate * @@ -186,7 +230,7 @@ class JWK ) ); - return sprintf( + return \sprintf( "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", wordwrap(base64_encode($pem), 64, "\n", true) ); @@ -208,6 +252,14 @@ class JWK ): string { $mod = JWT::urlsafeB64Decode($n); $exp = JWT::urlsafeB64Decode($e); + // Correct encoding for ASN1, as ints are represented as unsigned in jwk + // but signed in ASN1. Prepending null byte makes it unsigned. + if (\strlen($mod) > 0 && \ord($mod[0]) >= 128) { + $mod = \chr(0) . $mod; + } + if (\strlen($exp) > 0 && \ord($exp[0]) >= 128) { + $exp = \chr(0) . $exp; + } $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); @@ -241,7 +293,8 @@ class JWK * DER-encode the length * * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See - * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information. + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} + * for more information. * * @param int $length * @return string diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php index c83ff0990..f9a21e207 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php @@ -31,6 +31,8 @@ class JWT private const ASN1_SEQUENCE = 0x10; private const ASN1_BIT_STRING = 0x03; + private const RSA_KEY_MIN_LENGTH = 2048; + /** * When checking nbf, iat or expiration times, * we want to provide some extra leeway time to @@ -62,7 +64,8 @@ class JWT 'RS256' => ['openssl', 'SHA256'], 'RS384' => ['openssl', 'SHA384'], 'RS512' => ['openssl', 'SHA512'], - 'EdDSA' => ['sodium_crypto', 'EdDSA'], + 'PS256' => ['openssl', 'SHA256'], + 'EdDSA' => ['sodium_crypto', 'EdDSA'] ]; /** @@ -78,6 +81,7 @@ class JWT * Supported algorithms are 'ES384','ES256', * 'HS256', 'HS384', 'HS512', 'RS256', 'RS384' * and 'RS512'. + * @param stdClass $headers Optional. Populates stdClass with headers. * * @return stdClass The JWT's payload as a PHP object * @@ -86,7 +90,8 @@ class JWT * @throws UnexpectedValueException Provided JWT was invalid * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' - * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws BeforeValidException Provided JWT is trying to be used before it's + * been created as defined by 'iat' * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim * * @uses jsonDecode @@ -94,7 +99,8 @@ class JWT */ public static function decode( string $jwt, - $keyOrKeyArray + #[\SensitiveParameter] $keyOrKeyArray, + ?stdClass &$headers = null ): stdClass { // Validate JWT $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; @@ -102,7 +108,7 @@ class JWT if (empty($keyOrKeyArray)) { throw new InvalidArgumentException('Key may not be empty'); } - $tks = \explode('.', $jwt); + $tks = \explode('.', $jwt, 4); if (\count($tks) !== 3) { throw new UnexpectedValueException('Wrong number of segments'); } @@ -111,6 +117,9 @@ class JWT if (null === ($header = static::jsonDecode($headerRaw))) { throw new UnexpectedValueException('Invalid header encoding'); } + if ($headers !== null) { + $headers = $header; + } $payloadRaw = static::urlsafeB64Decode($bodyb64); if (null === ($payload = static::jsonDecode($payloadRaw))) { throw new UnexpectedValueException('Invalid claims encoding'); @@ -122,6 +131,16 @@ class JWT if (!$payload instanceof stdClass) { throw new UnexpectedValueException('Payload must be a JSON object'); } + if (isset($payload->iat) && !\is_numeric($payload->iat)) { + throw new UnexpectedValueException('Payload iat must be a number'); + } + if (isset($payload->nbf) && !\is_numeric($payload->nbf)) { + throw new UnexpectedValueException('Payload nbf must be a number'); + } + if (isset($payload->exp) && !\is_numeric($payload->exp)) { + throw new UnexpectedValueException('Payload exp must be a number'); + } + $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); @@ -147,24 +166,31 @@ class JWT // Check the nbf if it is defined. This is the time that the // token can actually be used. If it's not yet that time, abort. - if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { - throw new BeforeValidException( - 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf) + if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) { + $ex = new BeforeValidException( + 'Cannot handle token with nbf prior to ' . \date(DateTime::ATOM, (int) floor($payload->nbf)) ); + $ex->setPayload($payload); + throw $ex; } // Check that this token has been created before 'now'. This prevents // using tokens that have been created for later use (and haven't // correctly used the nbf claim). - if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { - throw new BeforeValidException( - 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat) + if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) { + $ex = new BeforeValidException( + 'Cannot handle token with iat prior to ' . \date(DateTime::ATOM, (int) floor($payload->iat)) ); + $ex->setPayload($payload); + throw $ex; } // Check if this token has expired. if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { - throw new ExpiredException('Expired token'); + $ex = new ExpiredException('Expired token'); + $ex->setPayload($payload); + $ex->setTimestamp($timestamp); + throw $ex; } return $payload; @@ -174,11 +200,11 @@ class JWT * Converts and signs a PHP array into a JWT string. * * @param array $payload PHP array - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' * @param string $keyId - * @param array $head An array with header elements to attach + * @param array $head An array with header elements to attach * * @return string A signed JWT * @@ -187,17 +213,27 @@ class JWT */ public static function encode( array $payload, - $key, + #[\SensitiveParameter] $key, string $alg, - string $keyId = null, - array $head = null + ?string $keyId = null, + ?array $head = null ): string { - $header = ['typ' => 'JWT', 'alg' => $alg]; + $header = ['typ' => 'JWT']; + if (isset($head)) { + $header = \array_merge($header, $head); + } + $header['alg'] = $alg; if ($keyId !== null) { $header['kid'] = $keyId; } - if (isset($head) && \is_array($head)) { - $header = \array_merge($head, $header); + if (isset($payload['nbf']) && !\is_numeric($payload['nbf'])) { + throw new UnexpectedValueException('Payload nbf must be a number'); + } + if (isset($payload['iat']) && !\is_numeric($payload['iat'])) { + throw new UnexpectedValueException('Payload iat must be a number'); + } + if (isset($payload['exp']) && !\is_numeric($payload['exp'])) { + throw new UnexpectedValueException('Payload exp must be a number'); } $segments = []; $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header)); @@ -214,9 +250,9 @@ class JWT * Sign a string with a given key and algorithm. * * @param string $msg The message to sign - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. - * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', - * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', 'PS256' and 'RS512' * * @return string An encrypted message * @@ -224,7 +260,7 @@ class JWT */ public static function sign( string $msg, - $key, + #[\SensitiveParameter] $key, string $alg ): string { if (empty(static::$supported_algs[$alg])) { @@ -236,10 +272,22 @@ class JWT if (!\is_string($key)) { throw new InvalidArgumentException('key must be a string when using hmac'); } + self::validateHmacKeyLength($key, $algorithm); return \hash_hmac($algorithm, $msg, $key, true); case 'openssl': + if ($alg === 'PS256') { + return self::signPS256($key, $msg); + } $signature = ''; - $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line + if (!$key = openssl_pkey_get_private($key)) { + throw new DomainException('OpenSSL unable to validate key'); + } + if (str_starts_with($alg, 'RS')) { + self::validateRsaKeyLength($key); + } elseif (str_starts_with($alg, 'ES')) { + self::validateEcKeyLength($key, $alg); + } + $success = \openssl_sign($msg, $signature, $key, $algorithm); if (!$success) { throw new DomainException('OpenSSL unable to sign data'); } @@ -250,20 +298,8 @@ class JWT } return $signature; case 'sodium_crypto': - if (!\function_exists('sodium_crypto_sign_detached')) { - throw new DomainException('libsodium is not available'); - } - if (!\is_string($key)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); - } try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $key)); - $key = base64_decode((string) end($lines)); - if (\strlen($key) === 0) { - throw new DomainException('Key cannot be empty string'); - } - return sodium_crypto_sign_detached($msg, $key); + return sodium_crypto_sign_detached($msg, self::validateEdDSAKey($key)); } catch (Exception $e) { throw new DomainException($e->getMessage(), 0, $e); } @@ -278,7 +314,8 @@ class JWT * * @param string $msg The original message (header and body) * @param string $signature The original signature - * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For Ed*, ES*, HS*, a string key works. + * for RS*, must be an instance of OpenSSLAsymmetricKey * @param string $alg The algorithm * * @return bool @@ -288,7 +325,7 @@ class JWT private static function verify( string $msg, string $signature, - $keyMaterial, + #[\SensitiveParameter] $keyMaterial, string $alg ): bool { if (empty(static::$supported_algs[$alg])) { @@ -298,7 +335,18 @@ class JWT list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'openssl': - $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line + if ($alg === 'PS256') { + return self::verifyPS256($keyMaterial, $msg, $signature); + } + if (!$key = openssl_pkey_get_public($keyMaterial)) { + throw new DomainException('OpenSSL unable to validate key'); + } + if (str_starts_with($alg, 'RS')) { + self::validateRsaKeyLength($key); + } elseif (str_starts_with($alg, 'ES')) { + self::validateEcKeyLength($key, $alg); + } + $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); if ($success === 1) { return true; } @@ -310,19 +358,8 @@ class JWT 'OpenSSL error: ' . \openssl_error_string() ); case 'sodium_crypto': - if (!\function_exists('sodium_crypto_sign_verify_detached')) { - throw new DomainException('libsodium is not available'); - } - if (!\is_string($keyMaterial)) { - throw new InvalidArgumentException('key must be a string when using EdDSA'); - } try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $keyMaterial)); - $key = base64_decode((string) end($lines)); - if (\strlen($key) === 0) { - throw new DomainException('Key cannot be empty string'); - } + $key = self::validateEdDSAKey($keyMaterial); if (\strlen($signature) === 0) { throw new DomainException('Signature cannot be empty string'); } @@ -335,6 +372,7 @@ class JWT if (!\is_string($keyMaterial)) { throw new InvalidArgumentException('key must be a string when using hmac'); } + self::validateHmacKeyLength($keyMaterial, $algorithm); $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); return self::constantTimeEquals($hash, $signature); } @@ -372,12 +410,7 @@ class JWT */ public static function jsonEncode(array $input): string { - if (PHP_VERSION_ID >= 50400) { - $json = \json_encode($input, \JSON_UNESCAPED_SLASHES); - } else { - // PHP 5.3 only - $json = \json_encode($input); - } + $json = \json_encode($input, \JSON_UNESCAPED_SLASHES); if ($errno = \json_last_error()) { self::handleJsonError($errno); } elseif ($json === 'null') { @@ -399,13 +432,28 @@ class JWT * @throws InvalidArgumentException invalid base64 characters */ public static function urlsafeB64Decode(string $input): string + { + return \base64_decode(self::convertBase64UrlToBase64($input)); + } + + /** + * Convert a string in the base64url (URL-safe Base64) encoding to standard base64. + * + * @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding) + * + * @return string A Base64 encoded string with standard characters (+/) and padding (=), when + * needed. + * + * @see https://www.rfc-editor.org/rfc/rfc4648 + */ + public static function convertBase64UrlToBase64(string $input): string { $remainder = \strlen($input) % 4; if ($remainder) { $padlen = 4 - $remainder; $input .= \str_repeat('=', $padlen); } - return \base64_decode(\strtr($input, '-_', '+/')); + return \strtr($input, '-_', '+/'); } /** @@ -420,7 +468,6 @@ class JWT return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); } - /** * Determine if an algorithm has been provided for each Key * @@ -432,7 +479,7 @@ class JWT * @return Key */ private static function getKey( - $keyOrKeyArray, + #[\SensitiveParameter] $keyOrKeyArray, ?string $kid ): Key { if ($keyOrKeyArray instanceof Key) { @@ -639,4 +686,150 @@ class JWT return [$pos, $data]; } + + /** + * Validate HMAC key length + * + * @param string $key HMAC key material + * @param string $algorithm The algorithm + * + * @throws DomainException Provided key is too short + */ + private static function validateHmacKeyLength(string $key, string $algorithm): void + { + $keyLength = \strlen($key) * 8; + $minKeyLength = (int) \str_replace('SHA', '', $algorithm); + if ($keyLength < $minKeyLength) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * Validate RSA key length + * + * @param OpenSSLAsymmetricKey $key RSA key material + * @throws DomainException Provided key is too short + */ + private static function validateRsaKeyLength(#[\SensitiveParameter] OpenSSLAsymmetricKey $key): void + { + if (!$keyDetails = openssl_pkey_get_details($key)) { + throw new DomainException('Unable to validate key'); + } + if ($keyDetails['bits'] < self::RSA_KEY_MIN_LENGTH) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * Validate RSA key length + * + * @param OpenSSLAsymmetricKey $key RSA key material + * @param string $algorithm The algorithm + * @throws DomainException Provided key is too short + */ + private static function validateEcKeyLength( + #[\SensitiveParameter] OpenSSLAsymmetricKey $key, + string $algorithm + ): void { + if (!$keyDetails = openssl_pkey_get_details($key)) { + throw new DomainException('Unable to validate key'); + } + $minKeyLength = (int) \str_replace('ES', '', $algorithm); + if ($keyDetails['bits'] < $minKeyLength) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial + * @return non-empty-string + */ + private static function validateEdDSAKey(#[\SensitiveParameter] $keyMaterial): string + { + if (!\function_exists('sodium_crypto_sign_verify_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $keyMaterial)); + $key = self::urlsafeB64Decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + return $key; + } + + /** + * Signs a message with a PS256 algorithm + * + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key + * @param string $message + * @throws DomainException Provided key is invalid + */ + private static function signPS256( + #[\SensitiveParameter] string|OpenSSLAsymmetricKey|OpenSSLCertificate $key, + string $message + ): string { + if (!class_exists('\phpseclib3\Crypt\RSA')) { + throw new DomainException('phpseclib/phpseclib is required for PS256 support'); + } + + if ($key instanceof OpenSSLCertificate) { + throw new DomainException('Cannot sign with an X.509 certificate. A private key is required.'); + } + + if ($key instanceof OpenSSLAsymmetricKey) { + if (!openssl_pkey_export($key, $pem)) { + throw new DomainException('OpenSSL unable to export the AsymmetricKey'); + } + $key = $pem; + } + + /** @var \phpseclib3\Crypt\RSA\PrivateKey $rsa */ + $rsa = \phpseclib3\Crypt\PublicKeyLoader::load($key); + + return $rsa->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS) + ->withHash('sha256') + ->sign($message); + } + + /** + * Validates a PS256 algorithm signature. + * + * @param string|OpenSSLAsymmetricKey|OpenSSLCertificate $key + * @param string $message + * @param string $signature + * @throws DomainException Provided key is invalid + */ + private static function verifyPS256( + #[\SensitiveParameter] string|OpenSSLAsymmetricKey|OpenSSLCertificate $key, + string $message, + string $signature + ): bool { + if (!class_exists('\phpseclib3\Crypt\RSA')) { + throw new DomainException('phpseclib/phpseclib is required for PS256 support'); + } + + if ($key instanceof OpenSSLAsymmetricKey) { + $details = openssl_pkey_get_details($key); + if (!$details || !isset($details['key'])) { + throw new DomainException('OpenSSL unable to extract public key'); + } + $key = $details['key']; + } elseif ($key instanceof OpenSSLCertificate) { + if (!openssl_x509_export($key, $pem)) { + throw new DomainException('OpenSSL unable to export certificate'); + } + $key = $pem; + } + + /** @var \phpseclib3\Crypt\RSA\PublicKey $rsa */ + $rsa = \phpseclib3\Crypt\PublicKeyLoader::load($key); + + return $rsa->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PSS) + ->withHash('sha256') + ->verify($message, $signature); + } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php new file mode 100644 index 000000000..7933ed68b --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php @@ -0,0 +1,20 @@ +keyMaterial = $keyMaterial; - $this->algorithm = $algorithm; } /** @@ -55,7 +44,7 @@ class Key } /** - * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate + * @return string|OpenSSLAsymmetricKey|OpenSSLCertificate */ public function getKeyMaterial() { diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/AGENTS.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/AGENTS.md new file mode 100644 index 000000000..564546fe3 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/AGENTS.md @@ -0,0 +1,76 @@ +# Agent Guidelines + +## Code and tooling + +- All code must remain compatible with PHP 7.4. Run PHPStan and PHP-CS-Fixer + only under a PHP 7.4.x runtime; never run either tool under any other PHP + major.minor version. +- Use fully qualified `#[\SensitiveParameter]` on concrete executable + parameters when their established role normally carries a secret, + credential-bearing aggregate, or confidential Guzzle-owned container, and + the active frame can throw or invoke throwing code. +- Repeat the attribute on every qualifying owned caller, callee, concrete trait + method, and closure parameter. Do not add it to interfaces, abstract-only + declarations, pure/no-realistic-throw helpers, assignment-only sites, + arbitrary generic payloads, or completed non-recoverable derivatives. +- For PHP 7.4 compatibility, put `#[\SensitiveParameter]` on its own line and + the parameter on the following line, expand the complete parameter list, and + never add a comma after the final parameter. Native trace redaction starts on + PHP 8.2 and does not redact logs, messages, properties, wire traffic, captured + variables, return values, or the separate backtrace `$this`/`object`. +- Always pass an explicit character list to `trim()`, `ltrim()`, and `rtrim()`; + never rely on the default characters. +- Handle `preg_*` engine failures: when the result is used as data, test for + `false` or `null` and throw a `\RuntimeException` including + `preg_last_error_msg()`; boolean validation guards must compare strictly, such + as `=== 1`, so an engine failure can only ever fail closed. Diagnostic + escaping is the narrow exception: use a deterministic bytewise fallback rather + than throwing, so it cannot obscure the original exception. +- Anchor validation patterns to the true end of input with the `D` modifier or + `\z`; a bare `$` accepts a trailing newline. +- Never embed raw control bytes in exception messages and other diagnostics; + escape or redact the offending value first. +- Helper classes that expose only public static methods are `final` and have a + private constructor. +- Resist native PHP serialization when a class holds live state (streams, + resources, handles, callbacks, credentials) or when magic methods such as + `__destruct()` have side effects that untrusted unserialized data could + redirect, as with the file and session write fixes in Guzzle's persisting + cookie jars. Plain data holders, such as Guzzle's in-memory cookie jar, remain + serializable. +- To resist, `__serialize()` and `__unserialize()` both throw + `\LogicException(static::class.' should never be serialized')` and its + unserialized counterpart, usually via the `@internal` `NonSerializableTrait`. + Where a `__destruct()` has such a side effect, it is enabled only by the + constructor and disabled in `__wakeup()` and `__unserialize()` before + throwing, so the protection holds even if the exception is swallowed. +- In general, numeric inputs should not accept non-finite floats. In situations + where they are accepted and we need to cast to a string, we should branch on + `\is_finite($value)`, using `(string) $value` for the finite case and + `\is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF')` otherwise. +- Never call `strtolower()`, `strtoupper()`, `strcasecmp()`, `stripos()`, or + other locale-sensitive case functions; use the locale-independent + `GuzzleHttp\Psr7\Utils::asciiToLower()`, `asciiToUpper()`, + `caselessEquals()`, and `caselessContains()` helpers instead. +- Changes in behavior need a `CHANGELOG.md` entry in the unreleased section of + the target branch and an `UPGRADING.md` note when the behavior differs between + major versions. + +## Contributor references + +- Read the [exception guidelines](docs/contributing/exception-guidelines.md) + before deciding which exception a change should throw. +- Read the + [cURL connection reuse reference](docs/contributing/curl-connection-reuse.md) + before working on the cURL handlers or connection reuse and sharing behavior. + +## Documentation + +- Wrap markdown prose and PHPDoc text to 80 columns using greedy wrapping. Never + split a markdown link or an inline code span across a line break; a line that + cannot be broken may exceed the limit. Avoid em dashes. +- Keep PHPDoc and the corresponding `docs/` pages in sync: shared prose is + deliberately word-for-word identical, including boilerplate copied verbatim + between related functions, so apply the same edit to every copy. Only + formatting and linking may differ, such as a docs link becoming a PHPDoc + `@see` tag; the wording must never drift. diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/CHANGELOG.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/CHANGELOG.md index 12949ba67..f411b2793 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/CHANGELOG.md +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -2,6 +2,758 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. + +## 8.3.1 - 2026-10-25 + +### Fixed + +- Fix `CookieJar` storing already expired cookies instead of removing the stored cookie +- Fix `CookieJar` ignoring a same-value cookie that changes its expiry or its `Discard`, `Secure`, or `HttpOnly` flag +- Fix `SetCookie` renewing the lifetime of `Max-Age` cookies rebuilt from an array, so persisted cookies never expired + + +## 8.3.0 - 2026-10-18 + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^3.1` +- Adjusted `guzzlehttp/psr7` version constraint to `^3.2` +- Replace the deprecated `Promise\Is` with `Promise\State` for PHP 8.6 +- Decode chunked stream handler bodies as they arrive, ending at the last chunk +- Reject truncated and malformed chunked stream handler bodies, like libcurl +- Reject unsupported response transfer codings, like libcurl 8.14+ +- End stream handler bodies at their declared `Content-Length`, including `0` +- Throw when a streamed body ends before its declared `Content-Length` +- Classify cURL errors 8 and 23 with a response as `ResponseTransferException` +- Reject stream handler gzip and deflate bodies with trailing data, like libcurl +- Accept raw deflate stream handler bodies, like libcurl + +### Fixed + +- Fix streamed stream handler bodies delaying bytes that arrived with the headers + + +## 8.2.1 - 2026-10-18 + +### Fixed + +- Fix PHP 8.6 notices for oversized numeric hosts +- Fix `delay` values over 71 minutes +- Fix `CurlMultiHandler` delays over 71 minutes +- Fix overlapping stream handler requests sharing a deadline +- Fix stream handler responses in Swoole coroutines on PHP 8.4+ +- Fix reads of streamed gzip and deflate responses waiting for 8 KiB of decoded data +- Fix stream handler accepting corrupt gzip and deflate response bodies + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^3.1.1` + + +## 8.2.0 - 2026-09-06 + +### Changed + +- Allow connection caps to be combined with persistent transport sharing on libcurl 8.22.0+ + + +## 8.1.0 - 2026-08-24 + +### Added + +- Add PHP 8.6+ stream TLS session sharing, while persistent sharing remains cURL-only +- Add support for PHP 8.6 +- Add in-transfer resends of seekable streamed uploads when PHP exposes `CURLOPT_SEEKFUNCTION` + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^3.0.2` +- Adjusted `guzzlehttp/psr7` version constraint to `^3.1` +- Classify stream handler transport failures using PHP 8.6+ structured stream error codes +- Hide URI credentials, queries, and fragments in automatic exception messages +- Match `no_proxy` rules against IPv4 hosts written in the shorthand a transport reads as an address +- Hold the cURL easy handle out of the reuse pool until a silent retry has been dispatched +- Treat a deferred resolved with a pending retry promise as progress when waiting on cURL transfers + + +## 8.0.3 - 2026-08-24 + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^3.0.1` + + +## 8.0.2 - 2026-08-05 + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^3.0.1` + +### Fixed + +- Fail a cURL multi handler wait with an attributable error when the transfer is no longer tracked +- Fix `StreamHandler` resolving numeric IPv4 hosts differently from cURL handlers on macOS and Windows +- Fix `StreamHandler` TLS peer names and proxy authorities for numeric IPv4 hosts on all platforms +- Settle a cURL multi handler transfer displaced by a request reusing its native handle ID + + +## 8.0.1 - 2026-07-26 + +### Security + +- Reject request URI hosts and `Host` header values that are not printable ASCII (GHSA-v5mv-p594-2x33) +- Reject URI hosts with a percent escape or that are not valid RFC 3986 hosts (GHSA-v5mv-p594-2x33) +- Reject numeric-looking URI hosts with trailing dots, read as IPv4 addresses (GHSA-v5mv-p594-2x33) +- Treat percent-escaped cookie domains as exact-match-only (GHSA-f7vp-7xgx-4w4r) +- Regenerate a derived `Host` header after client URI rewrites (GHSA-v5mv-p594-2x33) + + +## 8.0.0 - 2026-07-20 + +### Added + +- Add `SensitiveParameter` metadata to credential-bearing parameters so PHP 8.2+ redacts their values in exception traces +- Add HTTP/3 request support to the built-in cURL handlers when PHP 8.4+ and libcurl provide HTTP/3 support +- Add `Multiplexing::NONE` support as a client, cURL multi handler, and conditional request option +- Add generic and structured PHPDoc annotations to client request/config option, async promise, handler, middleware, pool, and mock handler APIs +- Add `ConnectTimeoutException` for connect-phase timeouts, extending `ConnectException` +- Add `NetworkException` for no-response network failures +- Add `NetworkTimeoutException` for no-response transport timeouts +- Add `ResponseTransferException`, with `ResponseTimeoutException` for response-transfer timeouts +- Add PSR-17 `request_factory`, `response_factory`, `stream_factory`, and `uri_factory` request options +- Add explicit `close()` lifecycle methods to the built-in cURL handlers and concrete cURL factory +- Add `HandlerClosedException` for pending transfers rejected by `CurlMultiHandler::close()` +- Add persistent transport sharing modes (`TransportSharing::PERSISTENT_PREFER` and `TransportSharing::PERSISTENT_REQUIRE`) +- Add `ProxyOptions` for proxy option resolution +- Add `ResponseException` for request failures with responses +- Add auth middleware for built-in Basic and Digest authentication + +### Changed + +- Canonicalize IPv6 hosts in Digest challenge cache keys +- Canonicalize IPv6 hosts in cookie domains, host-only identities, and domain matching +- Restrict cookie domain suffix matching to valid non-literal, nonnumeric host names +- Reject Secure cookies and insecure overlays received over insecure connections +- Enforce the `__Secure-` and `__Host-` prefix requirements on response cookies +- Hardened `FileCookieJar` and `SessionCookieJar` persistence against unsafe unserialization +- Reject native PHP serialization of runtime objects +- Restrict persisted `FileCookieJar` cookie files to owner-only permissions +- Require persisted cookie data to use JSON lists and construct all records before changing the jar +- Normalize persistent cookie JSON failures as `RuntimeException` +- Moved the internal `Utils` time, timeout, IDN, and environment helpers to dedicated internal classes +- Adjusted `guzzlehttp/promises` version constraint to `^3.0` +- Adjusted `guzzlehttp/psr7` version constraint to `^3.0` +- Quote multipart `Content-Type` boundary parameters when required +- Added parameter and return types to `SetCookie` methods +- Added native property types to supported public cURL handler state properties +- Added a `string` return type to `SetCookie::__toString()` +- Validate proxy and no-proxy option types strictly across handlers +- Match leading-dot no-proxy entries against the bare domain and split string no-proxy lists on whitespace +- Validate `force_ip_resolve`, protocols, and `delay` ranges at the client boundary +- Treat a matching proxy `no` entry as final even without a scheme-specific proxy entry +- Validate proxy URLs in the built-in handlers and reject malformed or unsupported ones up front +- Default a port-less proxy to 1080 in the stream handler, matching libcurl +- Downgrade HTTP/3 requests to HTTP/2 or HTTP/1.1 when the proxy is resolved from environment variables +- Throw `RequestException`, not `InvalidArgumentException`, for an unavailable proxy or TLS feature +- Resolve proxy environment variables in the stream handler, consistent with the cURL handlers +- Honor `no_proxy`/`NO_PROXY` from the environment in the stream handler, including `*` to disable proxying +- Reject an environment-resolved `https://` or SOCKS proxy in the stream handler, matching its `proxy` option behavior +- Require cURL proxy header separation for first-class `Proxy-Authorization` on every route +- Reject every first-class `Proxy-Authorization` field, including empty, on stream proxies +- Reject raw `CURLOPT_PROXYHEADER` without proxy header separation support +- Pass the request as the second argument to `on_headers` callbacks +- Pass the `Pool` iterable key as a trailing argument to per-request observer callbacks +- Declare strict types across remaining source files +- Reject request option values that do not match their documented types +- Reject invalid `idn_conversion`, `retries`, and built-in handler `on_stats` option values before use +- Reject non-finite floats in the `query` and `form_params` options +- Reject non-string scalar values in the `body` option +- Apply automatic `Expect: 100-Continue` injection to HTTP/1.1 requests only +- Reject invalid `SetCookie` constructor field types instead of coercing them +- Validate and normalize request framing across the built-in cURL and stream handlers +- Reject raw cURL request options outside the built-in cURL handlers' allow-list +- Reject non-string raw cURL header-list entries before applying them +- Reject proxy tunnels that require fresh connections when persistent transport sharing requires reuse +- Reject PHP stream context options outside the built-in stream handler allow-list +- Reject selected request options ignored by incompatible built-in handlers +- Treat only `null` as an omitted path or name when clearing cookies +- Validate malformed `auth` request option arrays +- Reject colons in built-in Basic usernames and ASCII control characters in Basic credentials +- Move built-in Basic and Digest authentication handling to the default auth middleware +- Reject unchallenged Digest probes for body-bearing requests instead of replaying the request unauthenticated +- Reject malformed Digest challenge parameter lists that libcurl's Digest parser may have tolerated +- Reuse Digest challenges to authorize subsequent body-less requests preemptively +- Advance the Digest nonce count when a stale challenge repeats the same nonce during the initial handshake +- Remove first-class NTLM authentication from the `auth` request option +- Stop forwarding the generic `auth` request option when following cross-origin redirects +- Limit the `Referer` header to the origin on cross-origin redirects +- Follow only redirect status codes 301, 302, 303, 307, and 308 +- Reject invalid `HandlerStack::remove()` arguments +- Require `Pool` request collections to be iterable +- Raised the built-in cURL handler floor to libcurl 7.34.0 with SSL support +- Store response cookies without a `Domain` attribute as host-only cookies +- Prefer cookie `Max-Age` over `Expires` when both attributes are present +- Match cookie names case-sensitively in `CookieJar::getCookieByName()` +- Ignore float-like or exponent `Max-Age` cookie values instead of truncating them +- Tighten invalid response handling and avoid exposing response-derived cURL stats +- Reject malformed response protocol versions and reason phrases +- Escape controls and malformed UTF-8 when copying raw values into exception messages +- Reject malformed or conflicting response `Content-Length` and combinations with `Transfer-Encoding` +- Expose raw stream-handler `Transfer-Encoding` metadata and coalesced framing in `progress` on newer PHP +- Wrap malformed redirect `Location` values in `BadResponseException` +- Default HTTPS requests sent by the built-in cURL and stream handlers to TLS 1.2 or newer +- Apply the stream handler `crypto_method` option through the SSL context so it consistently controls the minimum TLS version +- Validate built-in handler timeout options before applying them +- Require a request when constructing `TransferException` and its subclasses +- Classify empty, malformed, or handler-unsupported request protocol versions as request exceptions +- Classify additional cURL transport failures without a response as `NetworkException` +- Classify stream connect failures as `ConnectException`, with connect timeouts as `ConnectTimeoutException` +- Classify stream transport failures without a response as `NetworkException`, with timeouts as `NetworkTimeoutException` +- Classify generic response-aware request failures as `ResponseException` +- Classify response-aware transfer failures as `ResponseTransferException` +- The stream handler returns an empty body and releases the connection at the end of the headers for HEAD and CONNECT-2xx exchanges and 1xx, 204, and 304 responses +- The stream handler no longer writes to the `sink` option or reads trailing bytes for responses that cannot carry a body +- Reject short buffered stream-handler bodies against `Content-Length`, including decoded gzip/deflate +- Normalize duplicate `Content-Length` casings and preserve encoded values on decoded responses +- Reject unrepresentable byte counts and response sizes requiring integer bounds as `ResponseException` +- Ignore cURL informational responses other than `101 Switching Protocols` before the final response +- Treat response sink rewind failures as `ResponseException` and skip non-seekable sink rewinds +- Classify redirect request-body rewind failures as `ResponseException` +- Ignore stream source close failures after a complete response body transfer +- Throw `GuzzleHttp\Exception\InvalidArgumentException` for invalid built-in handler options +- Classify built-in cURL handle, `sink`, and HTTP/3 setup failures as `RequestException` +- Throw `ConnectTimeoutException` for connect timeouts +- Throw `NetworkTimeoutException` for cURL no-response timeout errors +- Throw `ResponseTimeoutException` for response-aware transfer timeouts +- Enforce the `timeout` option as a total transfer deadline in the stream handler when it buffers the response +- Reject stream handler responses whose header block arrives after the `timeout` deadline +- Stop consulting the `default_socket_timeout` ini setting in the stream handler +- Treat stream handler `read_timeout` as an idle timeout for every request stage, defaulting to 60 seconds +- Default the cURL connect timeout to 60 seconds, with `connect_timeout` set to `0` disabling it +- Stop the stream handler from injecting `User-Agent` and `From` header values from the `user_agent` and `from` ini settings +- Classify request-body stream size detection, read, stringification, and rewind failures as `RequestException` or `ResponseException` by phase +- Classify cURL response sink write failures, including timeouts, as `ResponseException` or `RequestException` by phase +- Treat request method names case-sensitively in built-in handler and redirect method-specific behavior +- Treat PHP resources passed as `sink` as caller-owned in the built-in cURL and stream handlers +- Use the configured PSR-17 URI factory when parsing redirect `Location` headers +- Allow built-in cURL handler `progress` callbacks to abort transfers with truthy return values +- Normalize built-in handler `progress` callback arguments to integer byte counts +- Reject built-in cURL `progress` throwables with `ResponseException` when a response exists, otherwise `RequestException` +- Release built-in cURL easy handles before invoking `on_stats` +- Prefer `CURLOPT_XFERINFOFUNCTION` for built-in cURL progress callbacks when available +- Made `MessageFormatter` final and required `Middleware::log()` formatters to implement `MessageFormatterInterface` +- Made `CurlFactory`, `CurlHandler`, `CurlMultiHandler`, `MockHandler`, and `StreamHandler` final +- Made static utility classes non-instantiable and declared `GuzzleHttp\Handler\Proxy` final +- Pass the request to `on_trailers` callbacks, reject non-callable `on_trailers` values, and wrap `on_trailers` callback exceptions in `ResponseException` +- Wait for in-progress HTTP/2-capable connections by default (`multiplex` defaults to `Multiplexing::WAIT`) +- Require libcurl 7.65.2 or newer for HTTP/2 requests so multiplex waiting is never silently unavailable +- Require libcurl 7.54.0 for HTTPS proxies and requests tunneled through HTTP proxies +- Suppress proxy CONNECT response headers for tunneled requests +- Point rejections of the raw `CURLOPT_PIPEWAIT` cURL option at the `multiplex` request option +- Reject raw `CURLMOPT_PIPELINING` in favour of the `multiplex` cURL multi handler option +- Reject required multiplexing when the final `CURLOPT_HTTPAUTH` mask permits NTLM +- Reject cURL multi options that the runtime libcurl cannot apply +- Reject unknown handler constructor options +- Reject invalid `select_timeout` cURL multi handler option values +- Reject raw cURL multi connection cap options in favour of the named options +- Parse `Set-Cookie` strings with RFC 6265 whitespace trimming +- Ignore valueless `Set-Cookie` attributes that require a value when parsing +- Trim only the trailing CRLF from the stream handler header block +- Fail streamed uploads immediately when the body cannot be resent for an auth challenge + +### Removed + +- Dropped support for PHP 7.2 and 7.3 +- Removed `Client::__call()`; use the typed HTTP verb methods or `request()`/`requestAsync()` +- Removed `ClientInterface::getConfig()`; the concrete `Client::getConfig()` remains available +- Removed support for the `GUZZLE_CURL_SELECT_TIMEOUT` environment variable; use `CurlMultiHandler`'s `select_timeout` option +- Removed support for the `handler` request option; configure the handler on the client +- Removed direct access to `CurlMultiHandler::$_mh`; pass `CURLMOPT_*` values through constructor `options` instead +- Removed `RedirectMiddleware::$defaultSettings`; use `RedirectMiddleware::DEFAULT_SETTINGS` +- Removed the deprecated `RetryMiddleware::exponentialDelay()` method +- Removed the deprecated `RequestException::wrapException()` method +- Removed the deprecated `Utils::describeType()` method +- Removed `Utils::jsonDecode()` and `Utils::jsonEncode()` in favor of native JSON functions +- Removed deprecated `GuzzleHttp` namespace functions in favor of native or class equivalents +- Removed `Utils::defaultCaBundle()`; rely on the system trust store or pass a bundle path via the `verify` option +- Removed `HandlerStack::__toString()` +- Removed `RequestException::getHandlerContext()` and `ConnectException::getHandlerContext()` +- Removed response access from `RequestException`; use `ResponseException` +- Removed `Utils::isHostInNoProxy()`; use `ProxyOptions` helpers for Guzzle 8 no-proxy matching +- Removed `Utils::isUriInNoProxy()`; use `ProxyOptions::isUriInNoProxy()` +- Removed `Handler\Proxy::wrapTlsFallback()`; the default handler stack selects the cURL or stream handler by TLS support automatically + + +## 7.15.1 - 2026-07-18 + +### Security + +- Preserve host-only cookie scope and require explicit persistence markers (GHSA-wm3w-8rrp-j577) +- Bound response cookie admission and generated `Cookie` headers (GHSA-f283-ghqc-fg79) +- Exclude URI fragments from `Referer` headers generated for redirects (GHSA-h95v-h523-3mw8) + + +## 7.15.0 - 2026-07-17 + +### Added + +- Added `Multiplexing::NONE` support as a client, cURL multi handler, and conditional request option + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^2.13` +- Use locale-independent ASCII folding for all case normalization and comparison +- Bound cURL upload reads to the declared `Content-Length` +- Sanitize the cURL error text exposed through exception handler context +- Fail closed when a named cURL multi connection cap cannot be applied +- Reject the request-level `CURLOPT_SHARE` cURL option when named connection caps are configured +- Strengthen old-libcurl SOCKS isolation for raw `CURLOPT_PRE_PROXY` and opaque share handles +- Isolate HTTP proxy tunnels from opaque shared connection caches +- Trigger runtime deprecations for previously deprecated functionality in 7.1.0 + +### Deprecated + +- Deprecated `Utils::jsonDecode()` and `Utils::jsonEncode()` in favor of native JSON functions +- Deprecated passing `CURLMOPT_PIPELINING` in the cURL multi handler `options` array +- Deprecated passing `CURLOPT_PROXYHEADER` without cURL proxy header separation support + +### Fixed + +- Defer cURL requests created from multi callbacks until native execution unwinds +- Fail synchronous waits from native cURL callbacks promptly instead of self-deadlocking +- Guard cURL multi handle removal against progress callbacks re-entering the handler +- Scope promise waits on the cURL multi handler to the awaited transfer +- Strip `Content-Length` and `Transfer-Encoding` when redirects discard the request body +- Stop re-applying the `delay` request option to followed redirects + + +## 7.14.2 - 2026-07-14 + +### Security + +- Prevent first-class and proxy URL credentials from reaching origins (GHSA-94pj-82f3-465w) + + +## 7.14.1 - 2026-07-13 + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^2.12.5` + +### Fixed + +- Fail closed when a proxy tunnel isolation cURL option cannot be applied +- Normalize Stringable proxy credential values before computing connection-reuse section signatures +- Restore conservative credential redaction for unparseable proxies with multiple `@` separators +- Redact request URI credentials from the stream handler connection error message +- Reject enabled response streaming (`stream => true`) on cap-configured stream handlers +- Distinguish CurlMultiHandler and StreamHandler outcomes in connection-cap custom-handler guidance +- Reject raw cURL options that conflict with explicit multiplexing guarantees +- Stop explicit multiplexing conflict checks faulting on non-array cURL multi `options` values +- Reject required multiplexing when the final `CURLOPT_HTTPAUTH` mask permits NTLM +- Require an integer `CURLMOPT_PIPELINING` when combined with explicit multiplexing +- Check the required multiplexing cleartext proxy rule against the final cURL configuration +- Bound cURL multi handler blocking selects by the earliest pending request delay +- Stop synchronous cURL multi handler waits blocking on other transfers once the target has settled +- Stop cURL multi completion processing double-settling promises canceled from completion callbacks +- Run ready promise queue tasks before sleeping for delayed cURL multi requests +- Avoid integer overflow in cURL multi delay timing on 32-bit platforms +- Roll back failed cURL multi handle attachment instead of leaving requests pending +- Release the cURL easy handle when the `on_stats` callback throws +- Normalize response trailer field names to lowercase with values in wire order +- Retain response trailers only when an `on_trailers` callback is configured +- Validate the `on_trailers` callback before starting a cURL transfer +- Reject the `on_trailers` request option on the stream handler, which cannot observe trailers +- Match cookies, proxy schemes, auth types, and header names with locale-independent ASCII folding +- Reject proxy option values that Guzzle cannot classify identically to ext-curl + + +## 7.14.0 - 2026-07-08 + +### Added + +- Added the `on_trailers` request option to expose parsed HTTP response trailers +- Added the `multiplex` request option with `Multiplexing::*` modes to control or require HTTP/2 multiplexing +- Added rejection of explicit `multiplex` requests when `CURLMOPT_PIPELINING` disables multiplexing +- Added the `max_host_connections` and `max_total_connections` client and cURL multi handler options + +### Changed + +- Redirects that discard the request body no longer require it to be rewindable +- Synchronous cURL multi handler requests no longer wait for other queued transfers +- Section SOCKS proxy connections by credentials on libcurl before 7.69.0 +- Reject request-level `CURLOPT_SHARE` when combined with authenticated SOCKS proxy configuration +- Redact proxy userinfo containing raw control bytes in cURL errors +- Check linked curl/libcurl NTLM support before applying NTLM auth +- Clarify that NTLM is deprecated by both Guzzle and curl/libcurl +- Remove deprecation for the raw cURL `CURLOPT_CERTINFO` option +- Warn when a cURL multi option cannot be applied + +### Deprecated + +- Deprecate the raw `CURLOPT_PIPEWAIT` cURL option in favour of the `multiplex` request option +- Deprecate unknown handler constructor options +- Deprecate invalid `select_timeout` cURL multi handler option values +- Deprecate raw cURL multi connection cap options in favour of the named options + + +## 7.13.3 - 2026-07-08 + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^2.5.1` +- Adjusted `guzzlehttp/psr7` version constraint to `^2.12.4` +- Pass explicit trim characters ahead of the PHP 8.6 trim default change + +### Fixed + +- Stop matching cookie domains against hosts with a trailing newline +- Reject HTTP status codes and certificate type extensions with a trailing newline +- Treat PCRE engine failures as invalid cookie names during cookie validation +- Report PCRE engine failures when formatting log messages +- Report PCRE engine failures when splitting `no_proxy` values + + +## 7.13.2 - 2026-07-05 + +### Fixed + +- Stop the cURL multi handler busy-waiting on request delays shorter than one second +- Stop cURL HEAD requests with request bodies hanging on responses that declare a content length +- The cURL handler no longer transmits request bodies on HEAD requests +- Preserve response headers when a response includes HTTP trailers +- Harden cURL response header block detection when HTTP trailers are received +- Corrected the PSR-7 class names in the Pool iterator exception +- Redirect body rewind failures no longer leak a bare `RuntimeException` + + +## 7.13.1 - 2026-06-29 + +### Fixed + +- Allow middleware to rewrite partial URIs before transports validate them + + +## 7.13.0 - 2026-06-29 + +### Added + +- Added the `crypto_method_max` request option to cap the maximum TLS protocol version +- Added HTTP QUERY redirect support, preserving method and body on 301 and 302 + +### Changed + +- Section proxy tunnel connection reuse by credential so distinct credentials never share a tunnel +- Isolate concurrent foreign cURL proxy tunnels added while another owner's tunnel is active +- Route credentialed HTTP(S) proxy Proxy-Authorization headers through cURL proxy header handling +- Reject request-level `CURLOPT_SHARE` when combined with authenticated HTTP/HTTPS proxy tunnel configuration +- Remove deprecation for raw cURL `CURLOPT_PREREQFUNCTION` callbacks when defined by PHP cURL +- Route TLS 1.2 `crypto_method` requests to the stream handler when cURL cannot select TLS 1.2 +- Reject final request URIs missing a scheme or host before transfer + +### Deprecated + +- Deprecate invalid protocols, force_ip_resolve, delay, cookies, and allow_redirects values + + +## 7.12.3 - 2026-06-23 + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^2.12.3` + +### Security + +- Treat IP and numeric cookie domains as exact-match-only (GHSA-g446-98w2-8p5w) + + +## 7.12.2 - 2026-06-23 + +### Fixed + +- Clamp out-of-range `Max-Age` so a very large value no longer overflows to an already-expired timestamp +- Use strict comparison in `CookieJar` conflict resolution so distinct numeric-string names don't overwrite +- Store a cookie whose `Domain` has a trailing dot on the origin host instead of silently discarding it +- Fix `StreamHandler` hard-failing on bracketed IPv6 literal hosts when `force_ip_resolve` is set +- Use strict cookie `Path` comparison so `CookieJar::clear()` with a numeric path keeps a distinct-path cookie +- Fixed cookie handling for falsey `Domain`, `Max-Age`, path, and name values +- Fixed `decode_content` handling for falsey string values +- Fixed deprecated request option values reaching built-in handlers before normalization + + +## 7.12.1 - 2026-06-18 + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^2.12.1` + +### Fixed + +- Reject proxy URLs with a malformed scheme in the cURL handlers instead of letting libcurl mishandle them + +### Security + +- Reject HTTPS proxies when the installed libcurl lacks HTTPS-proxy support (GHSA-wpwq-4j6v-78m3) +- Reject dot-only cookie `Domain` attributes as match-all (GHSA-cwxw-98qj-8qjx) + + +## 7.12.0 - 2026-06-16 + +### Added + +- Added `RequestOptions` constants for `curl`, `retries`, and `stream_context` + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^2.12` +- Constrain cURL transport sharing to safe libcurl DNS and SSL session support +- Resolve proxy environment variables in the cURL handlers; libcurl no longer reads the environment itself +- Ignore proxy environment variables when the `proxy` request option makes a decision +- Disable proxy environment variables on Windows SAPIs other than CLI (httpoxy hardening) +- Redact proxy credentials from cURL handler error messages, following `Psr7\Utils::redactUserInfo()` +- Normalize no-proxy domain and IP literal matching across the cURL and stream handlers + +### Deprecated + +- Deprecated the request-level `handler` option, which will be ignored in 8.0 +- Deprecated raw cURL request options outside the built-in cURL handlers' allow-list +- Deprecated the `CURLOPT_PROXYTYPE` cURL request option; set the proxy type via a scheme-prefixed proxy URL +- Deprecated PHP stream context options outside the built-in stream handler allow-list +- Deprecated passing `ntlm` as a built-in `auth` type +- Deprecated `Utils::describeType()` +- Deprecated non-finite floats in the `query` and `form_params` options; 8.0 rejects them +- Deprecated non-string scalar values in the `body` option; 8.0 rejects them + +### Fixed + +- Fix cURL TLS and HTTP/2 capability detection using libcurl feature checks +- Fix proxy `no` list matches being re-proxied through environment-configured proxies by libcurl +- Fix `no` list and `NO_PROXY` matching to support IP CIDR ranges, matching libcurl +- Fix the stream handler not applying scheme-less proxies and their credentials + + +## 7.11.2 - 2026-06-12 + +### Fixed + +- Fixed non-finite float values emitting coercion warnings on PHP 8.5 + + +## 7.11.1 - 2026-06-07 + +### Fixed + +- Ignore request-level `transport_sharing`, matching other unknown request options + + +## 7.11.0 - 2026-06-02 + +### Added + +- Added support for providing the `proxy` request option's `no` value as a comma-delimited string +- Added the `protocols` request option to restrict allowed URI schemes for request transfers +- Added `cert_type` and `ssl_key_type` request options for TLS certificate and private-key file types +- Added PHP stream handler support for the `ssl_key` request option +- Added transport sharing via the `transport_sharing` client and cURL handler options + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^2.5` +- Adjusted `guzzlehttp/psr7` version constraint to `^2.11` +- Allowed domainless `SetCookie` instances to be stored without wildcard request matching +- Changed no-proxy matching to respect request ports for host-and-port rules +- Prevented `CurlMultiHandler` destructors from throwing during cleanup +- Improved invalid response handling across handlers + +### Deprecated + +- Deprecated non-iterable `Pool` request collections, which will be rejected in 8.0 +- Deprecated non-uppercase easy request methods; 8.0 preserves method casing +- Deprecated non-string `headers` request option values, which will be rejected in 8.0 +- Deprecated empty `headers` request option value arrays, which will be rejected in 8.0 +- Deprecated empty and malformed request protocol versions, which will be rejected in 8.0 +- Deprecated conflicting raw cURL request options, including `CURLOPT_SHARE`, which will be rejected in 8.0 +- Deprecated scalar-coerced `idn_conversion` request option values, which will be rejected in 8.0 +- Deprecated invalid documented request option value types, which will be rejected in 8.0 +- Deprecated selected request options ignored by incompatible built-in handlers, which will be rejected in 8.0 +- Deprecated `RequestException::wrapException()`, which will be removed in 8.0 +- Deprecated `RetryMiddleware::exponentialDelay()`, which will be removed in 8.0 + + +## 7.10.6 - 2026-06-01 + +### Fixed + +- `CurlMultiHandler` now rejects the promise when `CurlFactory::finish()` throws, preserving sibling transfers +- `SetCookie` now normalizes unparseable `Expires` values to `null` instead of `false` +- Fix stream handler decoded `gzip`/`deflate` truncation by dropping invalid `Content-Length` + + +## 7.10.5 - 2026-05-27 + +### Fixed + +- Defer cURL multi cancellation cleanup until after progress callbacks return +- Classify additional stream handler connection failures as `ConnectException` + + +## 7.10.4 - 2026-05-22 + +### Fixed + +- Fix IPv6 literal matching in no-proxy rules +- Handle cURL multi completion messages without handles after cancelled transfers +- Fix magic client request methods such as `options()` to uppercase inferred HTTP methods + + +## 7.10.3 - 2026-05-20 + +### Fixed + +- Fail clearly when an HTTP response header line is invalid +- Remove middleware by name when the name is also a callable string +- Treat empty request protocol versions as HTTP/1.1 + + +## 7.10.2 - 2026-05-20 + +### Fixed + +- Normalize HTTP version request options before applying them to PSR-7 requests +- Use string values for headers generated by request preparation and response decoding + + +## 7.10.1 - 2026-05-19 + +### Fixed + +- Fail clearly when cURL options cannot be applied +- Fail clearly when the certificate option is malformed +- Fail clearly when JSON decode depth is invalid +- Fail clearly when session cookie data is malformed +- Fail clearly when the stream progress option is not callable +- Prevent response creation failures from exposing stale cURL responses + + +## 7.10.0 - 2025-08-23 + +### Added + +- Support for PHP 8.5 + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^2.3` +- Adjusted `guzzlehttp/psr7` version constraint to `^2.8` + + +## 7.9.3 - 2025-03-27 + +### Changed + +- Remove explicit content-length header for GET requests +- Improve compatibility with bad servers for boolean cookie values + + +## 7.9.2 - 2024-07-24 + +### Fixed + +- Adjusted handler selection to use cURL if its version is 7.21.2 or higher, rather than 7.34.0 + + +## 7.9.1 - 2024-07-19 + +### Fixed + +- Fix TLS 1.3 check for HTTP/2 requests + + +## 7.9.0 - 2024-07-18 + +### Changed + +- Improve protocol version checks to provide feedback around unsupported protocols +- Only select the cURL handler by default if 7.34.0 or higher is linked +- Improved `CurlMultiHandler` to avoid busy wait if possible +- Dropped support for EOL `guzzlehttp/psr7` v1 +- Improved URI user info redaction in errors + +## 7.8.2 - 2024-07-18 + +### Added + +- Support for PHP 8.4 + + +## 7.8.1 - 2023-12-03 + +### Changed + +- Updated links in docs to their canonical versions +- Replaced `call_user_func*` with native calls + + +## 7.8.0 - 2023-08-27 + +### Added + +- Support for PHP 8.3 +- Added automatic closing of handles on `CurlFactory` object destruction + + +## 7.7.1 - 2023-08-27 + +### Changed + +- Remove the need for `AllowDynamicProperties` in `CurlMultiHandler` + + +## 7.7.0 - 2023-05-21 + +### Added + +- Support `guzzlehttp/promises` v2 + + +## 7.6.1 - 2023-05-15 + +### Fixed + +- Fix `SetCookie::fromString` MaxAge deprecation warning and skip invalid MaxAge values + + +## 7.6.0 - 2023-05-14 + +### Added + +- Support for setting the minimum TLS version in a unified way +- Apply on request the version set in options parameters + + +## 7.5.2 - 2023-05-14 + +### Fixed + +- Fixed set cookie constructor validation +- Fixed handling of files with `'0'` body + +### Changed + +- Corrected docs and default connect timeout value to 300 seconds + + +## 7.5.1 - 2023-04-17 + +### Fixed + +- Fixed `NO_PROXY` settings so that setting the `proxy` option to `no` overrides the env variable + +### Changed + +- Adjusted `guzzlehttp/psr7` version constraint to `^1.9.1 || ^2.4.5` + + ## 7.5.0 - 2022-08-28 ### Added @@ -9,20 +761,30 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - Support PHP 8.2 - Add request to delay closure params + ## 7.4.5 - 2022-06-20 +### Fixed + * Fix change in port should be considered a change in origin * Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin + ## 7.4.4 - 2022-06-09 +### Fixed + * Fix failure to strip Authorization header on HTTP downgrade * Fix failure to strip the Cookie header on change in host or HTTP downgrade + ## 7.4.3 - 2022-05-25 +### Fixed + * Fix cross-domain cookie leakage + ## 7.4.2 - 2022-03-20 ### Fixed @@ -31,6 +793,7 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - Reject non-HTTP schemes in StreamHandler - Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve` + ## 7.4.1 - 2021-12-06 ### Changed @@ -42,6 +805,7 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950) + ## 7.4.0 - 2021-10-18 ### Added @@ -59,6 +823,7 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945) + ## 7.3.0 - 2021-03-23 ### Added @@ -71,6 +836,7 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872) + ## 7.2.0 - 2020-10-10 ### Added @@ -93,6 +859,7 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786) + ## 7.1.1 - 2020-09-30 ### Fixed @@ -104,6 +871,7 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - We dont connect curl `sink` on HEAD requests. - Removed some PHP 5 workarounds + ## 7.1.0 - 2020-09-22 ### Added @@ -126,14 +894,17 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version - `Utils::defaultCaBundle()` - `CurlFactory::LOW_CURL_VERSION_NUMBER` + ## 7.0.1 - 2020-06-27 * Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699) + ## 7.0.0 - 2020-06-27 No changes since 7.0.0-rc1. + ## 7.0.0-rc1 - 2020-06-15 ### Changed @@ -141,6 +912,7 @@ No changes since 7.0.0-rc1. * Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629) * Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675) + ## 7.0.0-beta2 - 2020-05-25 ### Added @@ -166,6 +938,7 @@ No changes since 7.0.0-rc1. * Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528) + ## 7.0.0-beta1 - 2019-12-30 The diff might look very big but 95% of Guzzle users will be able to upgrade without modification. @@ -199,15 +972,18 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440) * Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464) + ## 6.5.2 - 2019-12-23 * idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489) + ## 6.5.1 - 2019-12-21 * Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454) * IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424) + ## 6.5.0 - 2019-12-07 * Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143) @@ -217,11 +993,13 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348) * Deprecated `ClientInterface::VERSION` + ## 6.4.1 - 2019-10-23 * No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that * Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar` + ## 6.4.0 - 2019-10-23 * Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108) @@ -234,6 +1012,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335) * Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362) + ## 6.3.3 - 2018-04-22 * Fix: Default headers when decode_content is specified @@ -275,13 +1054,14 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684) * Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827) - + Minor code cleanups, documentation fixes and clarifications. + ## 6.2.3 - 2017-02-28 * Fix deprecations with guzzle/psr7 version 1.4 + ## 6.2.2 - 2016-10-08 * Allow to pass nullable Response to delay callable @@ -289,6 +1069,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Fix drain case where content-length is the literal string zero * Obfuscate in-URL credentials in exceptions + ## 6.2.1 - 2016-07-18 * Address HTTP_PROXY security vulnerability, CVE-2016-5385: @@ -299,6 +1080,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c a server does not honor `Connection: close`. * Ignore URI fragment when sending requests. + ## 6.2.0 - 2016-03-21 * Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`. @@ -318,6 +1100,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Bug fix: provide an empty string to `http_build_query` for HHVM workaround. https://github.com/guzzle/guzzle/pull/1367 + ## 6.1.1 - 2015-11-22 * Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler @@ -333,6 +1116,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Bug fix: fixed regression where MockHandler was not using `sink`. https://github.com/guzzle/guzzle/pull/1292 + ## 6.1.0 - 2015-09-08 * Feature: Added the `on_stats` request option to provide access to transfer @@ -367,6 +1151,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set. https://github.com/guzzle/guzzle/pull/1189 + ## 6.0.2 - 2015-07-04 * Fixed a memory leak in the curl handlers in which references to callbacks @@ -384,6 +1169,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Functions are now conditionally required using an additional level of indirection to help with global Composer installations. + ## 6.0.1 - 2015-05-27 * Fixed a bug with serializing the `query` request option where the `&` @@ -392,6 +1178,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c use `form_params` or `multipart` instead. * Various doc fixes. + ## 6.0.0 - 2015-05-26 * See the UPGRADING.md document for more information. @@ -416,6 +1203,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * `$maxHandles` has been removed from CurlMultiHandler. * `MultipartPostBody` is now part of the `guzzlehttp/psr7` package. + ## 5.3.0 - 2015-05-19 * Mock now supports `save_to` @@ -426,6 +1214,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated. * URL scheme is now always lowercased. + ## 6.0.0-beta.1 * Requires PHP >= 5.5 @@ -478,6 +1267,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * `GuzzleHttp\QueryParser` has been replaced with the `GuzzleHttp\Psr7\parse_query`. + ## 5.2.0 - 2015-01-27 * Added `AppliesHeadersInterface` to make applying headers to a request based @@ -488,6 +1278,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c RingBridge. * Added a guard in the Pool class to not use recursion for request retries. + ## 5.1.0 - 2014-12-19 * Pool class no longer uses recursion when a request is intercepted. @@ -508,6 +1299,7 @@ Please see [the upgrade document](UPGRADING.md) that describes all BC breaking c * Exceptions thrown in the `end` event are now correctly wrapped with Guzzle specific exceptions if necessary. + ## 5.0.3 - 2014-11-03 This change updates query strings so that they are treated as un-encoded values @@ -522,6 +1314,7 @@ string that should not be parsed or encoded (unless a call to getQuery() is subsequently made, forcing the query-string to be converted into a Query object). + ## 5.0.2 - 2014-10-30 * Added a trailing `\r\n` to multipart/form-data payloads. See @@ -541,7 +1334,9 @@ object). * Note: This has been changed in 5.0.3 to now encode query string values by default unless the `rawString` argument is provided when setting the query string on a URL: Now allowing many more characters to be present in the - query string without being percent encoded. See https://tools.ietf.org/html/rfc3986#appendix-A + query string without being percent encoded. See + https://datatracker.ietf.org/doc/html/rfc3986#appendix-A + ## 5.0.1 - 2014-10-16 @@ -554,6 +1349,7 @@ Bugfix release. * Fixed an issue where transfer statistics were not being populated in the RingBridge. https://github.com/guzzle/guzzle/issues/866 + ## 5.0.0 - 2014-10-12 Adding support for non-blocking responses and some minor API cleanup. @@ -635,6 +1431,7 @@ interfaces. argument. They now accept an associative array of options, including the "size" key and "metadata" key which can be used to provide custom metadata. + ## 4.2.2 - 2014-09-08 * Fixed a memory leak in the CurlAdapter when reusing cURL handles. @@ -770,7 +1567,7 @@ interfaces. ## 4.0.0 - 2014-03-29 * For information on changes and upgrading, see: - https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + https://github.com/guzzle/guzzle/blob/4.x/UPGRADING.md#3x-to-40 * Added `GuzzleHttp\batch()` as a convenience function for sending requests in parallel without needing to write asynchronous code. * Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`. @@ -803,7 +1600,7 @@ interfaces. ## 4.0.0-rc.1 - 2014-03-15 -* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 +* See https://github.com/guzzle/guzzle/blob/4.x/UPGRADING.md#3x-to-40 ## 3.8.1 - 2014-01-28 @@ -1077,7 +1874,7 @@ interfaces. ## 3.4.0 - 2013-04-11 -* Bug fix: URLs are now resolved correctly based on https://tools.ietf.org/html/rfc3986#section-5.2. #289 +* Bug fix: URLs are now resolved correctly based on https://datatracker.ietf.org/doc/html/rfc3986#section-5.2. #289 * Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289 * Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263 * Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264. diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/CLAUDE.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md index f287fa98d..64c403586 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/README.md @@ -1,10 +1,6 @@ ![Guzzle](.github/logo.png?raw=true) -# Guzzle, PHP HTTP client - -[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) -[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) -[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) +# Guzzle, PHP HTTP Client Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services. @@ -15,40 +11,13 @@ trivial to integrate with web services. - Can send both synchronous and asynchronous requests using the same interface. - Uses PSR-7 interfaces for requests, responses, and streams. This allows you to utilize other PSR-7 compatible libraries with Guzzle. -- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients. +- Supports PSR-18, allowing interoperability with other PSR-18 HTTP clients. - Abstracts away the underlying HTTP transport, allowing you to write environment and transport agnostic code; i.e., no hard dependency on cURL, PHP streams, sockets, or non-blocking event loops. - Middleware system allows you to augment and compose client behavior. -```php -$client = new \GuzzleHttp\Client(); -$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); - -echo $response->getStatusCode(); // 200 -echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8' -echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}' - -// Send an asynchronous request. -$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); -$promise = $client->sendAsync($request)->then(function ($response) { - echo 'I completed! ' . $response->getBody(); -}); - -$promise->wait(); -``` - -## Help and docs - -We use GitHub issues only to discuss bugs and new features. For support please refer to: - -- [Documentation](https://docs.guzzlephp.org) -- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle) -- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/) -- [Gitter](https://gitter.im/guzzle/guzzle) - - -## Installing Guzzle +## Installation The recommended way to install Guzzle is through [Composer](https://getcomposer.org/). @@ -57,38 +26,70 @@ The recommended way to install Guzzle is through composer require guzzlehttp/guzzle ``` - ## Version Guidance -| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | -|---------|----------------|---------------------|--------------|---------------------|---------------------|-------|--------------| -| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | -| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | -| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | -| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | -| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.2 | +| Version | Status | PHP Version | +|---------|--------------|--------------| +| 8.3 | Latest | >=7.4,<8.7 | +| 7.16 | Maintenance | >=7.2.5,<8.7 | +| 6.5 | End of Life | >=5.5,<8.0 | -[guzzle-3-repo]: https://github.com/guzzle/guzzle3 -[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x -[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 -[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 -[guzzle-7-repo]: https://github.com/guzzle/guzzle -[guzzle-3-docs]: https://guzzle3.readthedocs.io/ -[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/ -[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/ -[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/ +## Quick Start +```php +$client = new \GuzzleHttp\Client(); +$response = $client->request('GET', 'https://api.example.com/users/123'); + +echo $response->getStatusCode(); // 200 +echo $response->getHeaderLine('content-type'); // 'application/json' +echo $response->getBody(); // '{"id": 123, "name": "Ada"}' +``` + +For more examples, see the [Quick Start](docs/quick-start.md). + +## Documentation + +- [Quick Start](docs/quick-start.md) +- [Overview](docs/overview.md) +- [Request Options](docs/request-options.md) +- [Uploading Data](docs/uploading-data.md) +- [Cookies](docs/cookies.md) +- [Exceptions](docs/exceptions.md) +- [Guzzle and PSR-7](docs/guzzle-and-psr-7.md) +- [Handlers](docs/handlers.md) +- [Middleware](docs/middleware.md) +- [Testing Guzzle Clients](docs/testing-guzzle-clients.md) +- [FAQ](docs/faq.md) +- [Package Ecosystem](docs/package-ecosystem.md) +- [Upgrade Guide](UPGRADING.md) +- [Changelog](CHANGELOG.md) + +We use GitHub issues only to discuss bugs and new features. For support, use +[Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle), the +[#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on +[PHP-HTTP Slack](https://slack.httplug.io/), or +[Gitter](https://gitter.im/guzzle/guzzle). ## Security -If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information. +If you discover a security vulnerability within this package, please send an +email to security@tidelift.com. All security vulnerabilities will be promptly +addressed by the security maintainers listed in the +[Security Policy](https://github.com/guzzle/guzzle/security/policy). Please do +not disclose security-related issues publicly until a fix has been announced. ## License -Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. +Guzzle is made available under the MIT License (MIT). Please see +[License File](LICENSE) for more information. ## For Enterprise Available as part of the Tidelift Subscription -The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) +The maintainers of Guzzle and thousands of other packages are working with +Tidelift to deliver commercial support and maintenance for the open source +dependencies you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact dependencies you +use. +[Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md b/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md index 45417a7e1..65bc70754 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -1,6 +1,1540 @@ Guzzle Upgrade Guide ==================== +7.0 to 8.0 +---------- + +Guzzle 8 is a major release that raises the minimum PHP version, updates the +Guzzle dependency stack, adopts stricter PSR-7 header and request method +behavior, changes some network exception classification, and tightens validation +for request options, protocols, transport settings, cookies, and native method +signatures. It also adds generic PHPDoc types to async APIs for static analysis. + +#### PHP Version and Dependencies + +Guzzle 8 requires PHP `^7.4 || ^8.0`. Guzzle 7 supported PHP +`^7.2.5 || ^8.0`. + +Guzzle 8 also requires Guzzle Promises 3.x and Guzzle PSR-7 3.x. If your +application pins either package directly, update those constraints to allow the +new major versions. + +Read the [Guzzle PSR-7 3.x upgrade guide][psr7-upgrade-guide] as part of every +Guzzle 8 upgrade. Guzzle's normal request and response APIs use Guzzle PSR-7, so +PSR-7 changes can affect applications even when they do not instantiate PSR-7 +classes directly. This guide calls out the most common inherited PSR-7 changes, +but it does not repeat every PSR-7 behavior change. + +Pay particular attention to [that PSR-7 upgrade guide][psr7-upgrade-guide] if +your application constructs requests, modifies responses, sets headers, uploads +files, builds multipart requests, works with URIs, or inspects streams. + +Also read the [Guzzle Promises 3.x upgrade guide][promises-upgrade-guide] if you +use async requests, custom handlers, middleware, pools, or promise helpers. + +Guzzle 8 now requires `psr/http-factory:^1.0` directly. + +[psr7-upgrade-guide]: https://github.com/guzzle/psr7/blob/3.0/UPGRADING.md +[promises-upgrade-guide]: https://github.com/guzzle/promises/blob/3.0/UPGRADING.md + +#### IPv6 Origin Canonicalization + +Guzzle PSR-7 3.x serializes IPv6 hosts in native URIs in their RFC 5952 +canonical form, and its `UriComparator::isCrossOrigin()` canonicalizes +bracketed IPv6 literals from any PSR-7 implementation before comparing +origins. Redirects between equivalent spellings of one IPv6 address are +therefore same-origin in Guzzle 8: `Authorization` and `Cookie` headers, the +`auth` option, cURL authentication options, and the same-scheme `Referer` path +and query may be retained where Guzzle 7 stripped or reduced them, and absolute +Digest `domain` protection spaces can cover an equivalent spelling of the same +address. + +Guzzle 8 also keys its own host-scoped state, the Digest challenge cache and +the cookie jar, on the canonical IPv6 form, so equivalent spellings share one +entry instead of fragmenting state. A cached Digest challenge may be reused +preemptively across spellings instead of causing another 401 probe. Host-only +cookies extracted from one spelling match requests using another, persisted +cookie domain text is canonical, and equivalent cookie entries coalesce. Bare +IPv6 cookie domains, which the cookie API permissively accepts, canonicalize +without gaining brackets, and bare and bracketed forms remain distinct +identities. Cookie-domain matching remains exact-only for IP literals and IP +addresses, and DNS suffix matching now requires both the cookie domain and the +request host to be valid non-literal, nonnumeric host names. A host counts as +numeric on its rightmost label alone, so a cookie domain such as +`svc.0xdeadbeef` is exact-only in Guzzle 8 although Guzzle 7 reads it as a name +and matches its subdomains. + +Configured proxy endpoints are not canonicalized, no-proxy matching was +already representation-independent, and the URI text sent to the transport is +not rewritten: a foreign `UriInterface` that reports a noncanonical IPv6 +spelling is serialized as supplied. IPvFuture literals, zone-bearing values, +and invalid bracketed text keep their ASCII-case-folded textual identity in +these Digest and cookie comparisons. + +#### PSR-7 Header Values + +Guzzle 8 uses Guzzle PSR-7 3.x, and several of its behavior changes surface +through normal Guzzle client usage. This section summarizes the inherited PSR-7 +changes most likely to affect Guzzle users; read the +[Guzzle PSR-7 3.x upgrade guide][psr7-upgrade-guide] for the complete list. + +Header values passed through the `headers` request option or PSR-7 request APIs +must now be strings or non-empty arrays of strings. Empty strings remain valid +explicit header values, but empty arrays, `null`, `false`, integers, floats, and +other non-string values are no longer cast or accepted. + +If your application builds headers from configuration, user input, or typed +domain values, normalize them before creating or sending requests: + +```php +// 7.x, no longer accepted in 8.0 +$client->request('GET', '/', [ + 'headers' => ['Api-Version' => 1], +]); + +// 8.0 +$client->request('GET', '/', [ + 'headers' => ['Api-Version' => '1'], +]); +``` + +#### Request Method Casing + +Guzzle 8 preserves explicitly provided request method casing. HTTP method names +are case-sensitive, so Guzzle now sends the method exactly as provided and +applies built-in method-specific behavior only to exact standard method names +such as `GET`, `HEAD`, `POST`, and `PUT`. + +If you previously relied on `new Request('get', ...)` or +`$client->request('get', ...)` being sent or treated as `GET`, normalize the +method before constructing or sending the request: + +```php +$client->request('GET', 'https://example.com'); +``` + +The convenience methods such as `$client->get()`, `$client->post()`, and their +async variants continue to use uppercase standard methods. + +#### Auth Request Option Changes + +Digest authentication is no longer implemented with cURL `CURLOPT_HTTPAUTH` and +`CURLOPT_USERPWD`. Guzzle supports legacy non-session Digest challenges without +`qop` and challenges with `qop=auth`; session algorithms require `qop`. +`auth-int` is not supported. To use libcurl's native Digest implementation +instead, omit `auth` and configure cURL options directly with a cURL handler, +including `CURLOPT_HTTPAUTH => CURLAUTH_DIGEST` and `CURLOPT_USERPWD`. + +Guzzle 8 no longer treats `ntlm` as a built-in `auth` type. If an existing +NTLM-only integration must be kept temporarily, configure cURL options directly +with a cURL handler and a libcurl build that still supports NTLM: + +```php +$client->request('GET', '/', [ + 'curl' => [ + CURLOPT_HTTPAUTH => CURLAUTH_NTLM, + CURLOPT_USERPWD => 'username:password', + ], +]); +``` + +This is not a long-term migration path. curl/libcurl has deprecated NTLM +because it is weak, deprecated by Microsoft, and does not work over HTTP/2 or +HTTP/3. curl made NTLM opt-in in curl 8.20.0 and plans to remove support in +September 2026. + +Digest requests that carry a body send the initial unauthenticated probe with an +empty body and `Content-Length: 0`, as libcurl does. The body is sent on +authenticated attempts: once in the normal one-challenge handshake, and again if +a stale-nonce challenge must be retried. Because of this, non-seekable request +bodies work with Digest authentication unless a stale-nonce retry forces a +rewind. Followed redirects also rewind the body under the normal redirect rules, +independently of Digest. If the server answers the probe with anything other +than a 401 response, a 407 proxy challenge, or a followable redirect, the request +fails with a `GuzzleHttp\Exception\ResponseException` carrying that response, +because the probe did not represent the original request; remove the `auth` +option for endpoints that do not require authentication. See "Differences from +libcurl's Digest implementation" below. + +#### Differences from libcurl's Digest implementation + +Guzzle 8's Digest middleware deliberately diverges from libcurl, which +implemented the `auth` option's Digest support in Guzzle 7, in the following +ways: + +- **Unchallenged probes fail loudly.** Body-bearing requests are probed with an + empty body. If the server answers the probe with anything other than a 401 + response, a 407 proxy challenge, or a followable redirect, Guzzle throws + `GuzzleHttp\Exception\ResponseException` with that response attached. This + exception pre-empts the `ClientException` or `ServerException` that + `http_errors` would otherwise raise for such a response; `catch + (RequestException $e)` catches both. For unchallenged sub-300 probe responses + libcurl instead re-issues the request unauthenticated, which can cause the + server to process both the empty probe and the replay; for other statuses it + surfaces the probe response. If a server does not require authentication, + remove the `auth` option. +- **Probe payload headers.** The probe strips the payload-describing headers: + `Expect`, `Transfer-Encoding`, `Trailer`, `Content-Range`, + `Content-Encoding`, `Content-MD5`, `Digest`, `Content-Digest`, and + `Repr-Digest`, and forces `Content-Length: 0`. `Content-Type` is kept, as + libcurl does. libcurl's probe is bodyless but not header-identical: it keeps a + caller-supplied `Expect`, keeps HTTP/1.1 `Transfer-Encoding`, sending a + chunked probe with a zero chunk and no `Content-Length`, and forwards other + caller headers untouched. Guzzle's stripping is deliberately stricter. +- **`auth-int` is rejected.** Challenges offering only `qop=auth-int` are + ignored. libcurl selects `auth-int` when `auth` is absent but hashes an empty + entity body, providing no actual body integrity. +- **Challenge selection.** With multiple Digest challenges, Guzzle picks the + strongest supported algorithm. libcurl decodes the first Digest + `WWW-Authenticate` header field and ignores later Digest fields. Multiple + Digest challenges inside one field are parser-dependent in curl. +- **Strict challenge parsing.** Challenges with duplicated or malformed + parameters are rejected. When another usable Digest challenge is present, it + is used instead, always across separate `WWW-Authenticate` header fields and + in some same-field shapes. The 401 is returned untouched only when no usable + Digest challenge remains. libcurl is lenient: later duplicate values overwrite + earlier ones, `stale` and `userhash` flags are sticky once set, and malformed + tails are tolerated after enough valid material has been parsed. +- **Challenge reuse.** Cached challenges authorize later body-less requests + preemptively with incremented nonce counts; body-bearing requests always + re-probe, and only challenges that produced an authenticated success are + cached. libcurl kept Digest state per easy handle, which Guzzle 7 reset between + requests, so 7.x re-handshook every request. Guzzle honors the RFC 7616 + `domain` parameter when scoping reuse, and curl's Digest state has no `domain` + handling. Guzzle generates a fresh cnonce per Authorization header, where + curl's non-SSPI implementation reuses one cnonce per nonce while incrementing + `nc`. When `domain` is absent the cached challenge covers the whole origin, so + on origins that host multiple realms or trust boundaries a preemptive request + can disclose the username or userhash and password-derived response material + for one realm to another same-origin endpoint before it challenges; use + separate origins, a server-side `domain`, or disable reuse by replacing the + default auth middleware with `GuzzleHttp\Middleware::auth(false)` as shown in + the `auth` request option documentation. +- **Per-leg observability.** Each handshake leg is a separate transfer: + `on_stats`, `on_headers`, and `progress` fire per leg, and `delay` applies + once, before the first Digest leg: the probe, or a preemptive request when + challenge reuse applies. libcurl performed the whole handshake inside one + transfer with one stats callback. +- **Streaming sinks.** With `stream => true` plus a configured `sink`, a Digest + request drains the final body into the sink, protecting sinks from challenge + bodies on handlers that ignore `stream`. A non-digest request on the stream + handler leaves the sink untouched. +- **Proxy Digest (407) is not implemented.** The response passes through. The + built-in cURL handlers allow proxy credentials but not `CURLOPT_PROXYAUTH`, + and libcurl defaults proxy auth to Basic, so proxy Digest requires a custom + handler. + +Non-seekable request bodies work for the normal handshake because the probe never +consumes them. A stale-nonce retry after the body has been consumed still +requires a seekable body, the same limit libcurl has. + +Built-in Basic and Digest authentication continue to work for clients using +Guzzle's default handler or `GuzzleHttp\HandlerStack::create($handler)`, but +they are now applied by the default auth middleware instead of while the client +constructs the request. If you pass a raw custom handler directly to `Client`, +remove default middleware, or build a handler stack manually, Basic and Digest +authentication will only be applied when your stack includes +`GuzzleHttp\Middleware::auth()`. Unknown auth type strings are left in the +request options for custom middleware or custom handlers. + +Guzzle also validates non-empty auth arrays before applying them: indexes `0` +and `1` must be username and password strings, and index `2`, when present, must +be a string or `null`. Invalid auth arrays that previously emitted warnings, +coerced values, or did nothing now throw +`GuzzleHttp\Exception\InvalidArgumentException`. + +```php +// Valid: +$client->request('GET', '/', [ + 'auth' => ['username', 'password', 'basic'], +]); + +// Invalid in 8.0: +$client->request('GET', '/', [ + 'auth' => ['username'], +]); +``` + +Guzzle 8's built-in authentication middleware rejects Basic `auth` credentials +when the username contains a colon or either the username or password contains +an ASCII control character. Guzzle 7 encoded and sent these values. Colons +remain valid in passwords. + +Guzzle 8 also no longer forwards the generic `auth` request option when +automatic redirects cross origin. Guzzle already removed the `Authorization` and +`Cookie` headers and cURL HTTP authentication options on cross-origin redirects; +this now also applies to handler-visible `auth` state. + +Same-origin redirects continue to preserve `auth`. If an application or custom +handler intentionally reused `auth` across redirected origins, disable automatic +redirects or handle redirects manually so each origin receives explicit +credentials. + +#### Exception Hierarchy and Classification + +Some Guzzle 8.0 exception changes add intermediate classes, while others +intentionally reclassify specific failures. Broad `TransferException` and +`GuzzleException` catch blocks still catch all Guzzle transfer failures, and +`RequestException` still catches response-aware and other non-network request +failures. More specific catch blocks need auditing: Guzzle 8.0 splits timeout +failures into connect-phase, no-response network, and response-aware timeout +classes, and the built-in cURL and stream handlers classify more transport +failures by transfer phase. This section covers upgrading from Guzzle 7.x to +Guzzle 8.0. The `ConnectException` inheritance change happened earlier, in +Guzzle 7.0.0, when it moved out from under `RequestException`; see the 6.0 to +7.0 notes for that migration. The hierarchy changes are easiest to compare in +the two trees below. + +Guzzle 7.x uses this hierarchy: + +```text +. \RuntimeException +└── TransferException (implements GuzzleException) + ├── ConnectException (implements NetworkExceptionInterface) + └── RequestException (implements RequestExceptionInterface) + ├── BadResponseException + │ ├── ClientException + │ └── ServerException + └── TooManyRedirectsException +``` + +Guzzle 8.0 uses this hierarchy: + +```text +. \RuntimeException +└── TransferException (implements GuzzleException) + ├── HandlerClosedException + ├── NetworkException (implements NetworkExceptionInterface) + │ ├── ConnectException + │ │ └── ConnectTimeoutException + │ └── NetworkTimeoutException + └── RequestException (implements RequestExceptionInterface) + └── ResponseException + ├── BadResponseException + │ ├── ClientException + │ └── ServerException + ├── ResponseTransferException + │ └── ResponseTimeoutException + └── TooManyRedirectsException +``` + +`NetworkException` is new in Guzzle 8.0 as the base class for no-response +network failures. Throughout Guzzle 7.x, `ConnectException` implements +`Psr\Http\Client\NetworkExceptionInterface` directly and there is no +Guzzle-specific network base class. Reusable packages that support both Guzzle +7.x and 8.0 should catch `Psr\Http\Client\NetworkExceptionInterface` for +no-response network failures. Applications or packages that require Guzzle 8.0 +may catch `GuzzleHttp\Exception\NetworkException` for all no-response network +failures. Keep catching `ConnectException` only when handling connection +establishment failures specifically. + +Guzzle 8.0 also makes response-aware request failures explicit. Guzzle 7.x does +not have `ResponseException`, and response-aware request failures remain under +`RequestException` throughout Guzzle 7.x. In Guzzle 8.0, `ResponseException` is +the base class for request failures where response headers were received and a +response object is available. `ResponseTransferException` is used for +transfer-level failures after headers, including response-aware network, +protocol, content-decoding, partial-body, and response-body transfer failures. +`BadResponseException` and `TooManyRedirectsException` also extend +`ResponseException`; custom subclasses of those existing classes, or of +`ClientException` and `ServerException`, must not override the now-final +`ResponseException::__construct()`. + +Only this branch exposes response access: `RequestException` no longer stores +responses, no longer accepts a response constructor argument, and no longer has +`getResponse()` or `hasResponse()` methods. Catch `ResponseException`, or test +with `instanceof ResponseException`, before calling `getResponse()`. + +Timeout exception classes are now split by the transport phase the handler can +determine. `ConnectTimeoutException` is thrown for detected connect timeouts +(DNS resolution, TCP connect, proxy CONNECT, or TLS handshake). It extends +`ConnectException`, so code that catches `ConnectException` will also catch +connect timeouts. `NetworkTimeoutException` is thrown for other detected +transport timeouts before response headers are received. It extends +`NetworkException`, but not `ConnectException`. `ResponseTimeoutException` is +thrown for response transfer timeouts after response headers are received. It +extends `ResponseTransferException` and exposes the response. + +Timeouts that originate from caller-supplied PSR-7 streams are not transport +timeouts. Reading the request body stream, including size detection, +stringification, rewind, and upload reads, is a `RequestException` before a +response and a `ResponseException` after response headers. A slow response +`sink` write is a `ResponseException` once a response exists, or a +`RequestException` otherwise. + +If a handler throws `Error`, `TypeError`, or another non-`Exception` `Throwable` +before returning a promise, `Client::sendAsync()` returns a rejected promise. +Waiting on it rethrows the original throwable. During request and response body +handling outside native cURL callbacks, Guzzle still wraps `\Exception` failures +as `RequestException` or `ResponseException` where appropriate, while +non-`Exception` throwables propagate unchanged. Native cURL callbacks remain +different. A throwable from a cURL `sink` write is wrapped as +`ResponseException` when a response was received, or `RequestException` +otherwise, and the cURL handlers continue to report `CURLE_WRITE_ERROR` as +`on_stats` handler error data. + +`HandlerClosedException` is new in Guzzle 8.0. It extends `TransferException` +and is used when an explicitly closed `CurlMultiHandler` rejects transfers that +are still pending, including delayed transfers. Existing Guzzle 7.x code should +not normally see this exception just by upgrading. Guzzle 7.x did not have this +exception or a public cURL handler `close()` method, and destructor cleanup did +not reject pending promises. If you add deterministic cleanup with +`CurlMultiHandler::close()`, wait for or cancel outstanding transfers first, or +handle `HandlerClosedException` or `TransferException` for pending promises you +may still observe. + +When updating catch blocks for Guzzle 8.0, catch the more specific no-response +and response-aware failures before `RequestException`: + +```php +use GuzzleHttp\Exception\NetworkException; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Exception\ResponseException; +use GuzzleHttp\Exception\ResponseTransferException; + +try { + $client->request('GET', $uri); +} catch (NetworkException $e) { + // No-response network failures, including reclassified cURL and stream + // handler failures. +} catch (ResponseTransferException $e) { + $response = $e->getResponse(); + + // Transfer-level failures after response headers were received. +} catch (ResponseException $e) { + $response = $e->getResponse(); + + // Other request failures after response headers were received. +} catch (RequestException $e) { + // Request failures where Guzzle does not expose a response object. +} +``` + +The built-in handlers also reclassify several failures that Guzzle 7.x reported +as `RequestException` or `ConnectException`: + +- Connection-establishment failures (DNS and proxy resolution, TCP connect, TLS + setup, and QUIC connect) are `ConnectException`. +- Other failures with no response, such as send and receive errors and + no-response HTTP/2 and HTTP/3 protocol errors, are `NetworkException`. +- Response-transfer failures after response headers were received are + `ResponseTransferException`. This includes response-aware cURL connection, + network, protocol, content-decoding, partial-body, and response body transfer + failures. Response-body network stalls are `ResponseTimeoutException`, while + `sink` write failures, progress callback failures, deterministic response + size/platform-limit failures, or request-body stalls after headers are plain + `ResponseException` instances. +- Post-transfer response finalization failures are also plain + `ResponseException`. A seekable response sink that fails to rewind does not + become a `ResponseTransferException`. Non-seekable sinks are not rewound, and + source close cleanup after a complete stream-handler download is best effort. +- Other non-transfer or local failures that occur after a response was received + are `ResponseException`. + +This applies to both the cURL and stream handlers; the exact error codes and +messages each one maps onto these classes are an implementation detail. + +The cURL handler no longer treats non-`101` informational responses such as +`100 Continue`, `102 Processing`, or `103 Early Hints` as the final response. If +a cURL transfer fails after receiving only one of those interim responses, +Guzzle now reports a no-response failure such as `NetworkException` instead of a +`ResponseTransferException` carrying the interim `1xx` response. Code that +previously caught this path with `RequestException` or +`RequestExceptionInterface` should catch `NetworkExceptionInterface` or +`TransferException` instead. `101 Switching Protocols` is unchanged and is still +surfaced as a response. + +The built-in cURL and stream handlers now reject a response subject to body +framing when its `Content-Length` is malformed, conflicting, or present with +`Transfer-Encoding`. Equivalent decimal duplicates remain valid, including +leading zeros. Like libcurl 8.14 and newer, both handlers also reject a +`Transfer-Encoding` that names a coding other than `chunked` or `identity`, or +names `chunked` before another coding on the same header line. Framing failures +detected from a complete header block occur before `on_headers` sees that +response or any of its body bytes are delivered, and raise +`ResponseTransferException`. A transport that rejects the block first can +instead report its existing transfer failure. When a handler needs the length as +a PHP integer, a valid value above `PHP_INT_MAX` raises plain +`ResponseException` with the underlying `OverflowException` available through +`getPrevious()`. The stream handler does not impose this platform limit on +`stream => true` responses. Framing validation does not apply to responses to +`HEAD`, any `1xx`, `204`, `304`, or successful `CONNECT`. `205 Reset Content` +remains subject to framing validation. + +The stream handler also raises `ResponseTransferException` after draining a +non-streamed response when a valid, positive `Content-Length` declares more +bytes than it receives. For decoded gzip and deflate, the length applies to +encoded bytes before decompression, and the original values remain in +`x-encoded-content-length`. It raises the same exception when a chunked body +ends before its last chunk or has malformed chunk framing, or when a decoded +gzip or deflate body has data after the compressed stream; reading such a body +with `stream => true` throws a `\RuntimeException` once the bytes before the +fault have been returned. Valid `stream => true` responses remain lazy after +header validation, but end at a valid `Content-Length` that fits in a PHP +integer, and a read that reaches the end of the connection first throws a +`\RuntimeException`. A `Content-Length` of `0` gives an empty body; Guzzle 7 +read such a body until the connection closed. + +For chunked responses through the stream handler, the transport resource's +`wrapper_data` can now retain the original `Transfer-Encoding`. This is visible +on streamed bodies and to custom stream factories. On PHP 8.1.20+, 8.2.7+, and +8.3+, `progress` also counts raw chunk framing coalesced with the response +headers. Later socket reads were already counted before decoding. Guzzle's +response continues to expose the decoded body without that consumed header. A +streamed chunked body detached after it has been partly read is a Guzzle stream +resource that keeps decoding it, rather than the transport resource. + +The deprecated `RequestException::wrapException()` method was removed. Create a +`RequestException` directly for request failures where Guzzle does not expose a +response object. Its third constructor argument is now the exception code, +followed by the previous exception. For failures with a response, create +`ResponseException`, `ResponseTransferException`, `ResponseTimeoutException`, +`BadResponseException`, `ClientException`, `ServerException`, or +`TooManyRedirectsException` instead. If you used the removed +`getHandlerContext()` methods to classify failures, use the more granular +exception classes above instead, or use `on_stats` when you need handler timing +or statistics. `GuzzleHttp\Exception\InvalidArgumentException` remains outside +the transfer exception hierarchy and is still used for invalid configuration or +request option values that can be rejected before a transfer starts. + +`RequestException::create()` no longer accepts a handler-context array. Its +fourth argument is now the optional `BodySummarizerInterface`; remove any +handler-context argument and use the new exception classes or `on_stats` when you +need transport details. + +`TransferException` itself now also requires the failing request as its second +constructor argument, and its message argument is no longer optional; +`getRequest()` is available on the entire transfer exception hierarchy. The +subclasses' own constructors, such as `RequestException` and +`ConnectException`, already required a request in Guzzle 7, so only code +constructing the base class changes: replace `new TransferException('msg')` +with `new TransferException('msg', $request)`. + +#### Request Body Framing + +Before sending a body, the built-in cURL and stream handlers now finalize the +request framing. Each `Content-Length` value must be a non-negative decimal +integer that the selected transport can represent. Multiple values are allowed +only when they agree. Guzzle emits a single canonical value, and it must match +the available body size when that size is known. A request cannot include both +`Content-Length` and `Transfer-Encoding`. The only request transfer coding +Guzzle supports is a single `chunked` value on HTTP/1.1. Guzzle removes that +marker and adds an exact length when one is available. Otherwise, it lets the +transport choose valid wire framing. Other codings, coding chains, repeated +`chunked` values, unknown-length HTTP/1.0 bodies, and `chunked` on other HTTP +versions are rejected. + +A `Transfer-Encoding: chunked` header is only a provisional framing marker: +Guzzle treats the request body as content, never as pre-encoded chunks. Callers +that pre-encoded bodies for Guzzle 7's stream handler must remove the chunk +framing before upgrading, or the server receives it as literal content. + +For an unknown-size body with an explicit `Content-Length`, the stream handler +treats that length as the body boundary and does not read beyond it. Both +handlers reject premature EOF and request-body streams that return more bytes +than requested. The stream handler captures an otherwise unknown body once and +sends it with its exact length. Violations raise `RequestException` before a +response, or plain `ResponseException` if a cURL request-body read fails after +response headers. Omit both framing headers and let Guzzle choose. + +`PrepareBodyMiddleware` now adds a provisional `Transfer-Encoding: chunked` +marker only to unknown-size HTTP/1.1 bodies. For other protocol versions, custom +handlers receive no provisional framing header. + +A retry that reuses a consumed non-seekable body now fails if its known +remaining size no longer matches an explicit `Content-Length`. Use a seekable or +otherwise repeatable body for requests that may be retried. + +#### Request Option Validation + +Guzzle 8 rejects additional malformed request option values at the client +boundary. `force_ip_resolve` must be `v4` or `v6`; `protocols` and +`allow_redirects.protocols` may contain only `http` and `https`; and `delay` +must be finite and non-negative. Per-request `cookies` values must be `false` +or a `CookieJarInterface`; the `true` shorthand is only valid in the client +constructor. + +The following 33 options are now validated against their documented types +before a transfer starts. + +The boolean flags `http_errors`, `stream`, and `synchronous` must be `bool`. +`decode_content` and `verify` accept `bool` or `string`, `expect` accepts +`bool` or `int`, and `debug` accepts `bool` or a stream resource. +`allow_redirects` accepts `bool` or an array in which `max` is an `int`; +`strict`, `referer`, and `track_redirects` are `bool`; `on_redirect` is a +callable; and `protocols` is a non-empty array of `http` and `https` strings. + +`crypto_method`, `crypto_method_max`, and `retries` must be `int`. +`connect_timeout`, `read_timeout`, and `timeout` must be `int` or `float`, +`delay` must be a finite, non-negative `int` or `float`, and `version` accepts +`string`, `int`, or `float`. `cert_type` and `ssl_key_type` must be `string`. + +`cert` and `ssl_key` accept a `string` path or a `[path, password]` array +whose optional password may be a `string` or `null`. `auth` accepts `false`, a +`string`, or a `[username, password]` array of strings with an optional third +element that may be a `string` or `null`. + +`headers` must be an array whose values are strings or non-empty arrays of +strings. `form_params` must be an array of strings, numbers, booleans, `null`, +or nested arrays of the same, and its floats must be finite. `multipart` must +be an array of part arrays, each with a `string` or `int` `name`, a `contents` +entry, optional `string` header values, and an optional `string` `filename`. +`protocols` must be a non-empty array containing only `http` and `https`. +`curl` and `stream_context` must be arrays. + +`on_headers`, `on_stats`, `on_trailers`, and `progress` must be callables. +`sink` accepts a `string` path, a stream resource, or a `StreamInterface`, and +per-request `cookies` values must be `false` or a `CookieJarInterface` as +described above. Invalid values throw +`GuzzleHttp\Exception\InvalidArgumentException` naming the option and the +expected types. + +A few build- or version-specific rejections that previously threw +`InvalidArgumentException` now throw `RequestException`, matching how the +HTTP-version capability checks already behave: requesting `crypto_method` TLS +1.3 on a libcurl built without it, and a stream-handler proxy whose raw +transport this PHP build's `stream_get_transports()` does not provide (for +example `tls://` on a build without OpenSSL). The same input works on another +build, so it is a request failure rather than a caller error; a scheme invalid +on every build (such as `udp://` or `ftp://` for a proxy) still throws +`InvalidArgumentException`. Only code catching the specific exception type for +these build-misses needs to change. + +#### Per-request Handler Option + +Guzzle 7 deprecated the `handler` request option with a warning that Guzzle 8 +would ignore request-level handlers. Guzzle 8 rejects the option instead: +passing `handler` in per-request options throws +`GuzzleHttp\Exception\InvalidArgumentException` before a transfer starts. +Silently ignoring the option would fail open — a test that supplied a +per-request `MockHandler` would fall through to the client's real handler and +hit the network. Configure the handler when creating the client, or use a +separate client instance for requests that need a different handler: + +```php +// Guzzle 7 (deprecated, used the request-level handler) +$client->request('GET', '/status', ['handler' => $mockHandler]); + +// Guzzle 8 +$client = new Client(['handler' => HandlerStack::create($mockHandler)]); +$client->request('GET', '/status'); +``` + +#### Body Summaries In HTTP Error Exceptions + +Guzzle's default `http_errors` middleware uses `BodySummarizer` to include a +short response body summary in response-aware exception messages. In Guzzle 7, +creating that summary rewound seekable response bodies to the beginning. In +Guzzle 8, `BodySummarizer` still summarizes from the beginning of the body, but +then restores the body cursor to its previous position. + +Most applications do not need changes. Check only code that catches +response-aware exceptions from `http_errors` and then reads the response body +while relying on exception creation to leave that body rewound. If you need to +read the body from the beginning after catching the exception, call +`Message::rewindBody()` explicitly. + +#### Request Protocol Versions + +Invalid request protocol versions are no longer treated as omitted. Passing +`'version' => ''`, `'version' => 'HTTP/1.1'`, or sending a PSR-7 request whose +protocol version is empty or malformed now fails before the request is sent. + +Omit the `version` request option to use Guzzle's default HTTP/1.1 behavior, or +pass an explicit supported protocol version such as `'1.1'`. Do not include the +`HTTP/` prefix. + +Invalid `version` request option values are still rejected with +`GuzzleHttp\Exception\InvalidArgumentException` before a request is sent. +Empty or malformed protocol versions returned by a `RequestInterface`, and +well-formed but unsupported protocol versions such as HTTP/3 with the stream +handler, now fail with `GuzzleHttp\Exception\RequestException`. If you +previously caught `InvalidArgumentException` or `ConnectException` for request +protocol version failures, catch `RequestException` or `GuzzleException` +instead. + +#### Callback Semantics + +If you use the `progress` request option with the built-in cURL handlers, audit +callbacks for return values. Any truthy return value now aborts the transfer and +rejects the request with `ResponseException` when a response is available, or +`RequestException` otherwise. Return `0`, `false`, or nothing to keep the +transfer running. + +If a cURL `progress` callback throws, catch `ResponseException` for +response-aware failures or `RequestException` for broader request failures, and +inspect `getPrevious()` for the original throwable. The throwable no longer +escapes directly from the native cURL callback. + +The stream handler still ignores `progress` return values. + +Exceptions thrown by `on_stats` remain unwrapped, so existing catch logic for +`on_stats` exceptions does not need to change. The built-in cURL handlers now +release native easy handles before invoking `on_stats`. Built-in handlers now +reject non-callable `on_stats` values before starting the transfer, and the +built-in cURL handlers reject non-callable `on_trailers` values the same way. +Raw callbacks passed through the `curl` request option remain low-level cURL +callbacks and are not normalized by Guzzle. + +The `on_headers` request option callback now receives the request as its second +argument. Existing userland callbacks that accept only the response continue to +work, but callbacks that inspect all arguments, for example with +`func_get_args()` or a variadic parameter, will observe the additional +`Psr\Http\Message\RequestInterface` argument. + +The `on_trailers` request option callback now receives the request as its third +argument, after the trailer array and the response; callbacks that accept only +the first two arguments continue to work. Exceptions thrown by `on_trailers` +now reject the request with `ResponseException` instead of `RequestException`, +and the callback now runs before `on_stats` instead of after it, so `on_stats` +observes an `on_trailers` failure as the transfer's outcome. + +When requests are sent through `GuzzleHttp\Pool`, the pool now appends the +request's iterable key as one extra trailing argument to any `on_headers`, +`on_trailers`, `on_stats`, `progress`, and `allow_redirects.on_redirect` +callbacks provided via its "options" configuration; callable requests yielded +to the pool likewise receive the wrapped callbacks in their options argument. +Existing userland callbacks that do not declare the extra parameter continue +to work unchanged, but callbacks that inspect all arguments, for example with +`func_get_args()` or a variadic parameter, will observe the additional +argument, and arity-strict PHP internal callables used directly may reject the +extra argument and should be wrapped in a userland callback. `progress` return +values are still honoured, and requests sent directly through a client are +unaffected. + +The built-in handlers invoke `on_headers` for the final response headers and for +`101 Switching Protocols`, but not for other informational `1xx` responses such +as `100 Continue` or `103 Early Hints`. Guzzle does not expose a separate Early +Hints API; a dedicated interim-response hook may be added in a future minor +release. + +#### No-Content Response Bodies + +The stream handler never reads the response body of a HEAD request, of a 1xx, +204, or 304 response, or of a 2xx response to a CONNECT request. Such responses +now always carry an empty body created by the configured `stream_factory`; the +transport connection is closed as soon as the headers (and the `on_headers` +callback) complete successfully; the `sink` option is not opened or written +(string path sinks create no file); and `stream => true` yields the empty stream +rather than the live transport stream. In particular, `stream => true` no longer +exposes the live transport for a 2xx CONNECT response. Per RFC 9110 these +responses cannot carry content, so any bytes a misbehaving server sends after +the header section are never read. The cURL handler already behaved this way via +libcurl. + +#### HTTP/2 Multiplexing + +The `multiplex` request option defaults to `Multiplexing::WAIT`: +HTTP/2 requests on the cURL handlers set libcurl's `CURLOPT_PIPEWAIT`, so a +concurrent burst waits for an in-progress connection it may be able to share +instead of dialing one connection per request. Guzzle 7 leaves this to libcurl, +which never waits by default. Pass +`'multiplex' => Multiplexing::EAGER` to restore the old dialing +behaviour, or `Multiplexing::REQUIRE_EAGER`/`Multiplexing::REQUIRE_WAIT` to +fail loudly unless a multiplexed protocol is guaranteed. + +HTTP/2 requests also now require libcurl 7.65.2 or newer, so waiting is never +silently unavailable where HTTP/2 works. + +#### Sink Resource Ownership + +PHP resources passed as the `sink` request option are no longer closed when the +response body is closed or garbage-collected by the built-in cURL and stream +handlers. Applications that relied on Guzzle closing a raw resource sink should +close the resource explicitly or pass a string path instead. + +#### Stream Handler Header Serialization + +The stream handler now trims only the trailing CRLF pair from the serialized +header block it passes to the PHP stream context. Guzzle 7 also removed other +trailing whitespace there, which could drop trailing spaces or horizontal tabs +from the final header value when a request came from a PSR-7 implementation +that does not trim header values. + +#### TLS Minimum Version + +The built-in cURL and stream handlers now default HTTPS requests to TLS 1.2 or +newer. Applications that must connect to legacy TLS 1.0 or TLS 1.1 endpoints can +explicitly lower the minimum version with the `crypto_method` request option: + +```php +$client->request('GET', 'https://legacy.example.com', [ + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT, +]); +``` + +#### cURL Minimum Version + +Guzzle 8 requires libcurl 7.34.0 or higher with SSL/TLS support when using the +built-in cURL handlers. If this requirement is not met, the default handler +stack will not select cURL automatically, and manually configured cURL handlers +reject requests. + +`GuzzleHttp\Handler\Proxy::wrapTlsFallback()` has been removed. Custom handler +stacks no longer need it; the default handler stack selects the cURL or stream +handler based on this TLS support automatically. + +#### cURL Handler Lifecycle + +Applications that manage built-in cURL handlers or factories directly should +treat closed instances as unusable. Reusing a closed `CurlHandler`, +`CurlMultiHandler`, or `CurlFactory` throws `BadMethodCallException`; create a +new instance instead. + +If `CurlMultiHandler::close()` is called while transfers are pending, those +promises are rejected with `GuzzleHttp\Exception\HandlerClosedException`. +Destructor cleanup remains best-effort and does not reject pending promises. + +A custom `handle_factory` passed to a built-in cURL handler remains +caller-owned. Closing the handler does not close an injected factory. + +#### Timeout Option Validation + +Built-in handlers validate timeout option values when they apply those options. +`timeout` is applied by both built-in transports. `connect_timeout` is applied +by cURL handlers and accepted without effect by the stream handler. +`read_timeout` is applied by the stream handler and accepted without effect by +cURL handlers. When a built-in handler applies a timeout option, positive +values below `0.001` seconds now throw `InvalidArgumentException` instead of +being converted to no timeout. + +#### Stream Handler Timeout Enforcement + +The stream handler now enforces the `timeout` request option as a total +transfer deadline when it buffers the response, matching the cURL handlers' +treatment of the option as the total time of the request. Once the deadline +passes while the response body is being buffered, the transfer is aborted and +the request is rejected with `ResponseTimeoutException`. Each body read is +bounded by the shorter of the `read_timeout` idle timeout and the remaining +total timeout. A response whose header block arrives in small pieces past the +deadline is rejected once the headers are complete, because PHP's HTTP stream +wrapper can only bound the time between packets while waiting for response +headers. In Guzzle 7, the stream handler applies `timeout` only while +connecting and as the idle time between packets, so a server that keeps +sending small pieces of data can extend a request past the configured timeout +indefinitely and still produce a successful response. + +With the `stream` request option enabled, a response whose header block +completes past the deadline is rejected in the same way, and the response +carried by the exception has a closed body. The deadline does not apply to the +streamed response body, which is governed by the `read_timeout` idle timeout. + +#### Stream Handler Timeout Model + +The stream handler now treats `timeout` and `read_timeout` as orthogonal +controls. `timeout` is the total deadline for completing a buffered request or +obtaining a streamed response, with no deadline by default. `read_timeout` is +an idle timeout bounding how long the connection may sit silent at any stage, +while connecting, waiting for response headers, and between reads on the body, +defaulting to 60 seconds; `0` disables it. Any received data resets the idle +clock; nothing resets the deadline. + +In Guzzle 7, both behaviours fall back to the `default_socket_timeout` ini +setting (60 seconds by default), `timeout` also acts as the idle cap, and +`read_timeout` only governs streamed reads. Set `read_timeout` explicitly to +tune or disable idle protection; `default_socket_timeout` no longer has any +effect on the stream handler. + +#### Connect Timeout Default + +The built-in cURL handlers now default the connect timeout to 60 seconds +instead of libcurl's 300, and `connect_timeout` set to `0` now disables the +connect timeout instead of selecting the 300-second libcurl default, matching +how `0` disables the `timeout` and `read_timeout` options. The stream handler +still accepts `connect_timeout` without effect; its connect phase is bounded +by the `read_timeout` idle timeout, which shares the 60-second default. + +#### Stream Handler Header Injection + +The stream handler no longer lets PHP's HTTP stream wrapper inject header +values from ini configuration. A request without a User-Agent header no longer +sends the `user_agent` ini value, and the `from` ini value is no longer sent: +the wrapper offers no way to omit the From header entirely, so deployments +that configure the ini send an empty From header instead of the configured +address. Both now match the cURL handlers, which ignore those ini settings. +Set the headers explicitly on the request or client to send them. + +#### Proxy Option Validation + +The `proxy` request option is validated more strictly. Proxy values must be +strings, and the `proxy['no']` value may be either an array of strings or a +comma- or whitespace-delimited string such as the value from the `NO_PROXY` +environment variable. Other values now throw `InvalidArgumentException`. +Guzzle 7 skips invalid `no` entries instead of rejecting them. + +When a request uses HTTP/3 and a proxy is resolved from the environment, the +request is now downgraded to HTTP/2 or HTTP/1.1 in the same way as for proxies +configured through the `proxy` request option, since current libcurl releases +cannot carry HTTP/3 over an HTTP proxy. A request excluded by the environment +`no_proxy` list stays direct and keeps HTTP/3. + +#### No-Proxy Interpretation + +No-proxy lists are interpreted identically everywhere they appear — the option's +`no` list, the client-mapped `NO_PROXY` environment variable, and the +environment `no_proxy` consulted by the built-in handlers — and the shared +interpretation follows libcurl's. Compared to Guzzle 7, this changes how string +lists are tokenized, how leading-dot entries match, and what a match means. + +String `no` lists are split on whitespace as well as commas, and a leading-dot +entry such as `.example.com` now matches `example.com` as well as its +subdomains, exactly like the bare `example.com` entry. Both changes align the +option's matching with libcurl's interpretation of `no_proxy`, which the +environment path already followed. In Guzzle 7, the client mapping removes +spaces from `NO_PROXY` values, while the option form treats space-joined +values as a single entry that never matches and limits leading-dot entries to +subdomains. + +A matching `no` entry now applies even when the array does not configure a proxy +for the request scheme: the request goes direct, and the built-in handlers do +not fall back to an environment proxy for it. The `no` list is also validated in +that case. Guzzle 7 ignores the `no` list entirely unless the array selects a +proxy for the request scheme. + +#### Proxy URL Validation + +The stream handler now throws `InvalidArgumentException` for any proxy URL +whose scheme it cannot execute: `https://`, SOCKS (`socks4://`, `socks4a://`, +`socks5://`, `socks5h://`), and anything other than `http://` or a raw PHP +transport such as `tcp://`, `ssl://`, or `tls://`. Guzzle 7 passed these to +PHP, which failed later with an "unable to find the socket transport" error. +The cURL handlers reject a `proxy` URL whose scheme libcurl cannot use as a +proxy with the same exception. Raw PHP transport values still pass through the +stream wrapper unchanged. + +Both handlers also reject a malformed proxy URL (an invalid host, an +out-of-range port, or leading junk before the scheme) up front with the same +`InvalidArgumentException`, instead of passing it to libcurl or PHP to fail on +later. + +An `http://` or scheme-less proxy that omits the port now defaults to 1080, +libcurl's default HTTP proxy port, so `proxy.example.com` resolves to +`tcp://proxy.example.com:1080`. Guzzle 7 passed a port-less proxy through +unchanged, which PHP's stream wrapper could not use because it requires an +explicit port. + +#### Proxy Tunnels and HTTPS Proxies + +Requests tunneled through an HTTP proxy, meaning an `https://` target sent +through an `http://` or `https://` proxy, or a tunnel forced with raw `curl` +options, now require libcurl 7.54.0 or newer. HTTPS proxies (an `https://` +proxy URL, where the connection to the proxy itself is encrypted) also now +require libcurl 7.54.0 built with HTTPS-proxy support, raised from the 7.52.0 +floor used in Guzzle 7. The built-in cURL handlers reject such requests on +older libcurl with a `RequestException` before they are sent, matching the +other build- and version-specific capability checks. + +On supported libcurl, the proxy's CONNECT reply is suppressed with +`CURLOPT_SUPPRESS_CONNECT_HEADERS`. Guzzle 7 delivered the interim +`200 Connection established` header block to the `on_headers` callback and +could attach it to exceptions as a response. In Guzzle 8, `on_headers` +observes only origin responses, and a tunneled transfer failure is classified +by its transport phase instead of as a response failure carrying the proxy's +interim reply. + +#### Proxy-Authorization Headers + +In Guzzle 8, every first-class `Proxy-Authorization` field handled by a built-in +cURL handler requires libcurl 7.37.0 or newer with proxy header separation +support, regardless of Guzzle's predicted route. This includes an empty field, +which keeps its cURL header-control meaning in the proxy-only list, and requests +predicted to be direct, no-proxy-bypassed, or sent through SOCKS. A request is +rejected with a `RequestException` before network I/O when separation is not +available. Guzzle 7 instead omits the field on those known non-HTTP-proxy routes +and requires separation only when it cannot safely discard the field. + +Guzzle 8 also rejects a raw `CURLOPT_PROXYHEADER` list when proxy header +separation is unavailable. Guzzle 7.15 only deprecates the raw option and passes +it through to the installed PHP cURL extension and libcurl. + +When the stream handler selects a proxy, Guzzle 8 rejects any first-class +`Proxy-Authorization` field, including an empty value, because PHP streams have +no proxy-only header channel. Guzzle 7 instead accepts exactly one value, +rejects carriage returns and line feeds, and adds one canonical proxy +authorization line after selecting the proxy. That first-class value, including +an empty one, takes precedence over Basic credentials in proxy URL userinfo; +multiple values are rejected. Direct and no-proxy-bypassed stream requests omit +the field in both versions. Configure Basic proxy credentials in the proxy URI +or use a cURL handler for a first-class proxy authorization field. + +#### Proxy Tunnels Under Shared Connection Caches + +From libcurl 7.57.0, a cURL share handle passed directly to `CurlFactory` and +Guzzle's worker-global persistent sharing pools are treated as opaque +connection caches: anonymous HTTP and HTTPS proxy tunnels are forced onto +fresh, non-reusable connections, and `TransportSharing::PERSISTENT_REQUIRE` +rejects them. Guzzle 7 applies the same policy to its configured share handles +and additionally rejects the deprecated request-level `CURLOPT_SHARE` option +for proxy tunnels there; Guzzle 8 rejects that raw option outright, so only +the configured and persistent surfaces exist. Handler-lifetime +`transport_sharing` keeps ordinary tunnel reuse because its shares never lock +connections. + +#### Proxy Environment Variable Resolution + +The stream handler now resolves proxies from the environment the same way the +cURL handlers do. When the `proxy` request option makes no decision for a +request, it reads `http_proxy` (lowercase only), `https_proxy`/`HTTPS_PROXY`, +and `all_proxy`/`ALL_PROXY`, and consults `no_proxy`/`NO_PROXY` — including `*` +to disable proxying entirely. The uppercase `HTTP_PROXY` is never read (an +httpoxy defense), empty values are treated as unset, and on Windows proxy +environment variables are resolved only under the CLI SAPI. Guzzle 7's stream +handler never consulted the environment; it honored only the explicit `proxy` +option. + +Because the stream handler cannot tunnel or speak TLS to a proxy, an +`https_proxy` or `all_proxy` set to an `https://` or SOCKS URL now throws +`InvalidArgumentException`, exactly as the same value passed through the `proxy` +option already does, instead of connecting directly. An environment `http://` +proxy used for an "https" request still fails at connect time, because the +handler cannot open a CONNECT tunnel. + +To ignore the proxy environment variables for a request or client, set the +`proxy` option to `''`: an explicit option decision is final, so this forces a +direct connection with no environment fallback. To send only selected hosts +directly, add them to the `proxy` option's `no` list, or list them in +`no_proxy`/`NO_PROXY` (`*` bypasses every host). These work identically on both +built-in handlers. + +#### Handler-Specific Option Overrides + +Handler-specific overrides remain available for finer transport control when +they do not conflict with Guzzle-managed behavior. The built-in cURL handlers +now reject raw cURL options that override request method, URI, body, headers, +timeouts, redirects, proxy URLs and types, TLS verification or client +credentials, progress/debug callbacks, sink handling, cookies, protocols, +connection coalescing, or cURL share handles. Use first-class Guzzle request +options for those settings. Allowed raw cURL header-list options, such as +`CURLOPT_PROXYHEADER`, now accept only strings or stringable objects as entries. + +The cURL handlers also reject stream-only `stream_context` options, but accept +`read_timeout` without effect. The stream handler rejects cURL-only options it +cannot honor, but accepts `connect_timeout` without effect. These timeout options +are intentionally best-effort so shared request configuration can be reused +across transports. + +#### Expect: 100-Continue Injection + +Automatic `Expect: 100-Continue` injection now applies only to HTTP/1.1 requests. +HTTP/1.0, HTTP/2, and HTTP/3 requests do not receive the header from Guzzle's +body-preparation middleware. + +The stream handler rejects an explicit `expect` option when it results in an +`Expect` header, because PHP streams do not support the `100-Continue` workflow. +Set `expect => false` for stream-handler requests that must avoid this rejection. + +#### Native Type Declarations + +Guzzle 8 adds native parameter and return types where PHP 7.4 allows. Code +overriding affected methods must update method signatures accordingly. + +`SetCookie::__toString()` now returns `string`. + +`HandlerStack::remove()` now throws `TypeError` when passed a value that is +neither a callable nor a middleware name string. + +`Pool::__construct()` and `Pool::batch()` now require iterable request collections. + +`SetCookie::setSecure()`, `SetCookie::setDiscard()`, and +`SetCookie::setHttpOnly()` now require boolean parameters. Calls from files that +declare strict types will throw `TypeError` for non-boolean values. + +`SetCookie::getExpires()` now returns `int|null`. Invalid textual expiration +dates are treated as `null`. + +#### IDN Conversion Option Types + +The `idn_conversion` request option must be `true`, `false`, `null`, or an +integer `IDNA_*` bitmask. Numeric strings and floats that previously worked +through PHP scalar coercion are no longer accepted. + +Integer `0` remains a valid option bitmask. Use `false` or `null` to disable IDN +conversion. + +#### Generic Promise and Structured PHPDoc Types + +Guzzle's async client APIs, handlers, and middleware callable annotations now use +generic `PromiseInterface` PHPDoc types. This is a +static-analysis-only change at runtime, but projects with stricter static +analysis may see new or different diagnostics. + +Code using unparameterized promise types continues to work. If your project +implements Guzzle client interfaces, provides custom handlers or middleware, or +uses stricter static analysis, you may need to update your PHPDoc annotations to +include promise fulfillment and rejection types. + +Public client config, request option, pool option, handler, middleware, mock +handler, and history middleware PHPDoc now uses structured array and callable +shapes. This does not change runtime behavior, but stricter static analysis may +now report invalid option keys, invalid option value types, or lower-arity +callback annotations that were previously hidden behind loose `array` or +`callable` PHPDoc. + +If your project implements `ClientInterface`, extends client behavior through +traits, builds custom handlers or middleware, or documents reusable request +option arrays, update those PHPDoc annotations to match the supported request +option and callback shapes. + +#### Multipart Request Serialization + +Guzzle 8 uses Guzzle PSR-7 3.x for multipart request bodies. Multipart parts +created through the `multipart` request option no longer include generated +per-part `Content-Length` headers by default. If your tests compare raw +multipart payloads, remove those generated part headers from expected strings. + +Generated multipart `Content-Disposition` header `name` and `filename` +parameters now escape double quotes, carriage returns, and line feeds as `%22`, +`%0D`, and `%0A`. Literal backslashes and other characters are serialized +unchanged, matching browser multipart form submission behavior. Custom multipart +part header names and values, and explicit PSR-7 multipart boundaries, are also +validated by PSR-7. + +Custom multipart part header values also preserve trailing spaces and tabs in the +serialized request body, so raw body snapshots or signatures may need updated +expectations. + +Guzzle now quotes the `boundary` parameter in generated +`Content-Type: multipart/form-data` headers when an explicit PSR-7 +`MultipartStream` boundary contains characters that require quoting. +Automatically generated boundaries are unchanged. + +You can still pass an explicit `Content-Length` header in a multipart element's +`headers` array if a non-standard peer requires it. + +#### Cross-Origin Redirect Referer Header + +With the optional `referer` redirect setting enabled (off by default), Guzzle +now sends only the request origin (scheme, host, and port) in the `Referer` +header on a cross-origin redirect. It previously sent the referring URI, +including the path, query string, and fragment, which could leak secrets such +as reset tokens or signed query parameters to the new origin. + +Same-origin redirects still send the path and query, with any user information +and fragment removed. The `Referer` header is omitted entirely when the scheme +changes, including an `https` to `http` downgrade. This matches the +`strict-origin-when-cross-origin` policy that modern browsers use by default. + +If you relied on the full URL crossing origins, collect it with the +`on_redirect` setting, or disable automatic redirects and follow them manually. + +#### Automatic Redirects + +Guzzle now follows only the redirect status codes 301, 302, 303, 307, and 308 +when `allow_redirects` is enabled. Other 3xx responses, including 300, 304, 305, +and 306, are returned to the caller unchanged even when they carry a Location +header, in line with RFC 9110 section 15.4. Code that relied on Guzzle following +one of those responses should handle it directly or inspect it with an +on_redirect callback. + +#### Secure Cookie Integrity + +Guzzle 8 ignores `Secure` cookies received over non-HTTPS requests. It also +ignores an insecure cookie received over a non-HTTPS request when its name and +domain overlap an existing `Secure` cookie and its path falls within the +existing cookie's protected path. This includes deletions sent with +`Max-Age=0`. + +Guzzle 7 accepted these cookies. Applications using plain HTTP development +servers that send `Secure` cookies must use HTTPS or remove the attribute. +Cookies inserted directly through `CookieJar::setCookie()` or restored from +persistence are not rejected because they have no response origin. Existing +`Secure` records still protect against later cookies received over HTTP. + +#### Cookie Name Prefixes + +`CookieJar::extractCookies()` now recognizes the `__Secure-` and `__Host-` +prefixes case-insensitively. `__Secure-` response cookies must be `Secure`. +`__Host-` response cookies must also be host-only, include a `Path` attribute, +and use the root path. A bare `Path` without `=` remains ignored. Invalid +prefixed response cookies are ignored. + +Cookie names remain case-sensitive, so differently cased names remain distinct. +Correct the attributes or rename a legacy response cookie that used a reserved +prefix without satisfying its requirements. Cookies inserted directly through +`CookieJar::setCookie()` and restored persisted records are unchanged. + +#### Host-Only Cookies + +Cookies extracted from responses without a `Domain` attribute are now stored as +host-only cookies. They are sent only to the exact host that set them. + +Previously, Guzzle stored these cookies with the request host as a normal domain +cookie, so they could also be sent to subdomains. Applications relying on that +behavior should use an explicit `Domain` attribute. + +`SetCookie::toArray()` may include `HostOnly => true` for host-only cookies. + +#### Cookie Domain Normalization + +Cookie domains with multiple leading dots, such as `Domain=..example.com`, are +no longer normalized twice during matching. Such malformed domains are rejected +instead of matching `example.com` or its subdomains. + +#### Cookie Max-Age Expiration + +Cookies with `Max-Age=0` or a negative `Max-Age` are now treated as immediately +expired. When such a cookie is added to a `CookieJar`, it removes a matching +stored cookie instead of being retained as a normal cookie. + +#### SetCookie Constructor Field Validation + +`SetCookie` constructor arrays no longer coerce invalid field values. Cookie +names, values, domains, paths, max-age values, expiry values, and boolean flags +must use the documented types. Invalid constructor values now throw +`InvalidArgumentException`. + +```php +// Valid: +new SetCookie([ + 'Name' => 'foo', + 'Value' => 'bar', + 'Domain' => 'example.com', + 'Secure' => true, +]); + +// Invalid in 8.0: +new SetCookie([ + 'Name' => false, + 'Value' => 'bar', +]); +``` + +Cookies parsed from normal `Set-Cookie` headers continue to be normalized by +`SetCookie::fromString()`. Attributes that require a value (`Domain`, `Path`, +`Expires`, `Max-Age`) are ignored when they appear without one. + +#### SetCookie Max-Age Precedence + +`SetCookie::fromString()` now follows RFC cookie precedence when a `Set-Cookie` +header carries both `Max-Age` and `Expires`. A valid `Max-Age` value controls +the effective expiration time, including `Max-Age=0` expiring the cookie +immediately, even when `Expires` is a future date. Cookies built from an array +keep a supplied `Expires`, as in Guzzle 7, so persisted cookies do not restart +their lifetime when a jar is loaded. + +#### Cookie Max-Age Parsing + +`SetCookie::fromString()` now ignores float-like or exponent `Max-Age` values +such as `0.5`, `1.5`, or `1e3`. Guzzle 7 truncated these numeric forms toward +zero. Use integer-second `Max-Age` values. + +#### Set-Cookie Parsing Whitespace + +`SetCookie::fromString()` now trims cookie names, cookie values, and attribute +segments with the RFC 6265 whitespace characters, space and horizontal tab. +Guzzle 7 also trimmed line feeds, carriage returns, null bytes, and vertical +tabs. Header values produced by Guzzle PSR-7 cannot contain those bytes, so +this only affects strings passed to `SetCookie::fromString()` directly. + +#### CookieJar::clear Null Semantics + +`CookieJar::clear()` now treats only `null` as an omitted path or name. +Previously, falsy path or name values such as `'0'` or `''` could be interpreted +as omitted and clear a broader set of cookies than intended. + +If you call `clear()` to clear all cookies, continue passing no arguments: + +```php +$jar->clear(); +``` + +If you pass a path or name, that value is now treated as provided. + +#### Cookie Name Lookup + +`CookieJar::getCookieByName()` now matches cookie names case-sensitively. If a +jar contains distinct cookies such as `SID` and `sid`, retrieve each one with the +exact stored name. + +#### Cookie Jar Persistence + +`FileCookieJar` and `SessionCookieJar` can no longer be restored with +`unserialize()`; attempting to unserialize either jar now throws a +`LogicException`. Rebuild persisted jars instead: `new FileCookieJar($path)` +reloads the persisted cookie file when it exists, and +`new SessionCookieJar($key)` reloads the cookie data stored in the session. + +`FileCookieJar` now writes its cookie file with owner-only permissions (`0600`), +so persisted cookies are not world-readable under the default umask; if another +user or process must read the file, adjust its permissions after saving. Saved +cookie files also JSON-escape tag characters without changing cookie values. + +Persisted cookie data must now be a JSON list. Each list entry must decode to an +array, and recognized `SetCookie` fields must use their documented constructor +types. Malformed JSON, an invalid stored shape, or a recognized field with the +wrong type causes a `RuntimeException`. Cookie records are constructed before +any are passed to `setCookie()`, so such failures leave the jar unchanged. +Numeric or string-keyed JSON objects must be converted to lists. + +Every cookie record must include an explicit boolean `HostOnly` marker. The +built-in jars write this marker automatically. Delete or rotate older nonempty +data without it, or annotate each record only when its original `Domain` +semantics are known. + +In Guzzle 7, malformed JSON in a cookie file and `FileCookieJar` encoding +failures threw `GuzzleHttp\Exception\InvalidArgumentException`. Now they throw +`RuntimeException`, consistently with other persistent cookie failures. Both +jars expose the native `JsonException` through `getPrevious()` when JSON +encoding or decoding fails. + +An empty cookie file remains a no-op. A missing or `null` session value still +means no stored cookie data. Any other session value must be a string containing +a JSON list; malformed JSON and an empty string are rejected. `SessionCookieJar` +does not replace the stored value when construction fails. + +#### Logging Middleware Formatter Types + +`GuzzleHttp\MessageFormatter` is now final. Applications that extended +`MessageFormatter` should implement `GuzzleHttp\MessageFormatterInterface` +instead and pass the custom formatter to `GuzzleHttp\Middleware::log()`. + +`Middleware::log()` now requires its formatter argument to implement +`MessageFormatterInterface`. Passing `new MessageFormatter()` still works +because `MessageFormatter` implements `MessageFormatterInterface`. Passing any +other value now fails with PHP's native `TypeError` instead of Guzzle's previous +`LogicException`. + +```php +use GuzzleHttp\MessageFormatterInterface; +use GuzzleHttp\Middleware; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +final class RedactingFormatter implements MessageFormatterInterface +{ + public function format( + RequestInterface $request, + ?ResponseInterface $response = null, + ?\Throwable $error = null + ): string { + return $request->getMethod().' '.$request->getUri()->getPath(); + } +} + +$stack->push(Middleware::log($logger, new RedactingFormatter())); +``` + +#### Built-in Handler Inheritance + +`GuzzleHttp\Handler\CurlFactory`, `GuzzleHttp\Handler\CurlHandler`, +`GuzzleHttp\Handler\CurlMultiHandler`, `GuzzleHttp\Handler\MockHandler`, and +`GuzzleHttp\Handler\StreamHandler` are now final. + +Applications that extended `CurlFactory` should implement +`GuzzleHttp\Handler\CurlFactoryInterface` instead. Applications that extended +`CurlHandler`, `CurlMultiHandler`, `MockHandler`, or `StreamHandler` should use +composition instead: wrap a handler instance in a custom callable or provide a +custom handler rather than subclassing the built-in handler. + +#### Custom cURL Handle Factories + +Custom `GuzzleHttp\Handler\CurlFactoryInterface` implementations that create or +mutate `GuzzleHttp\Handler\EasyHandle` instances must assign values compatible +with EasyHandle's documented public property types. Several EasyHandle +bookkeeping properties now use native property types, so assigning incompatible +values to those properties raises `TypeError`. + +Custom factories must assign the request and sink state before returning an +EasyHandle to Guzzle because those properties are required typed invariants. +Reading them before assignment raises PHP's uninitialized typed-property `Error`. + +The native cURL handle properties intentionally remain untyped because PHP 7.4 +represents cURL handles as resources while PHP 8 represents them as cURL handle +objects. Custom factories must still unset `$easy->handle` when releasing an +easy handle, as required by `CurlFactoryInterface::release()`. + +#### Built-In Handler Constructor Options + +`CurlHandler`, `CurlMultiHandler`, and `StreamHandler` now reject unknown +constructor option keys with `GuzzleHttp\Exception\InvalidArgumentException`. +Remove misspelled or application-specific keys before constructing built-in +handlers. + +`CurlMultiHandler` now rejects cURL multi options that cannot be applied by the +installed runtime libcurl. Values passed through the constructor `options` key +must be an array keyed by integer `CURLMOPT_*` constants. Guzzle 7 already +fails closed when a named connection cap cannot be applied; Guzzle 8 extends +this rejection to every cURL multi option, including raw `CURLMOPT_*` entries +that Guzzle 7 only warns about. + +`CurlMultiHandler` now rejects `CURLMOPT_PIPELINING` in the constructor +`options` array, deprecated since Guzzle 7.15. Pass `Multiplexing::NONE` as the +`multiplex` client option or, when constructing the handler yourself, as the +`multiplex` constructor option to disallow multiplexing on the handler +(replacing `CURLPIPE_NOTHING` and `0`), or remove the option entirely for +multiplex-capable behavior (replacing masks containing `CURLPIPE_MULTIPLEX`); +multiplexing is on by default from libcurl 7.62, except on 7.65.0 and 7.65.1 +where a regression dropped the default, and Guzzle 8's HTTP/2 floor of libcurl +7.65.2 is the version that restored it, so removing the option preserves +multiplex-capable behavior on every supported HTTP/2 runtime. `CURLPIPE_HTTP1` +and `1` also map to `Multiplexing::NONE` on libcurl 7.62 and newer, where +HTTP/1.1 pipelining is a no-op; on older libcurl they enabled HTTP/1.1 +pipelining, which has no replacement. A handler configured with +`Multiplexing::NONE` wins over the default `WAIT` request mode: default requests +run without waiting, explicitly requested wait modes are rejected as a +configuration conflict when the transfer would actually wait, and the required +modes are always rejected. `Multiplexing::NONE` is also accepted as a request +option value exactly where its guarantee - the transfer does not share its +connection with any concurrent transfer - holds and can be verified; see the +`multiplex` request option documentation for the acceptance rules. + +`CurlMultiHandler` now rejects `CURLMOPT_MAX_HOST_CONNECTIONS` and +`CURLMOPT_MAX_TOTAL_CONNECTIONS` entries in the constructor `options` array. Use +the `max_host_connections` and `max_total_connections` client options when +Guzzle creates the default handler, or the same named `CurlMultiHandler` +constructor options when constructing the handler directly. + +Direct magic access to `CurlMultiHandler::$_mh` has been removed. This was an +undocumented internal lazy cURL multi handle. Applications that used it to set +`CURLMOPT_*` options should pass those values through the `options` key of the +`CurlMultiHandler` constructor. + +#### Progress Callback Parameter Types + +The built-in handlers now pass integer byte counts to `progress` callbacks. +Callbacks with `int` parameter types continue to work, and callbacks with +`float` parameter types can still receive integer byte counts in PHP. If a +callback used other scalar parameter types, update it to accept integers or +remove the scalar parameter declarations. + +#### CurlMultiHandler Select Timeout + +The `GUZZLE_CURL_SELECT_TIMEOUT` environment variable is no longer read. Pass +the `select_timeout` option to `CurlMultiHandler` instead. The +`select_timeout` option must be numeric, finite, and non-negative. It must be +`0` or greater than or equal to `0.001` seconds. + +#### Removed Client::__call and ClientInterface::getConfig + +`Client::__call()` has been removed. The typed HTTP verb methods (`get()`, +`head()`, `put()`, `post()`, `patch()`, `delete()`, and their `*Async()` +variants) have been real methods since Guzzle 7.0 and are unaffected. Only +verbs without a typed method lose their magic form: calls such as +`$client->options($uri)`, `$client->trace($uri)`, +`$client->optionsAsync($uri)`, or any custom verb such as +`$client->purge($uri)` now fail with a PHP undefined method error. Use +`request()` or `requestAsync()` with an explicit method instead: + +```php +// 7.x +$response = $client->options('http://example.com'); + +// 8.0 +$response = $client->request('OPTIONS', 'http://example.com'); +``` + +`ClientInterface::getConfig()` has been removed from the interface. The +concrete `Client::getConfig()` method remains available and is no longer +deprecated. Code reading configuration through a `ClientInterface`-typed +value must type against `Client` instead, and custom `ClientInterface` +implementations no longer need to provide `getConfig()`, although keeping the +method still satisfies the interface. + +#### Removed Function and JSON Helper APIs + +The deprecated `GuzzleHttp` namespace functions were removed, along with the +`functions.php` and `functions_include.php` files and the Composer `files` +autoload entry that loaded them on every request. + +Replace namespaced function calls with their native or class equivalents: + +```php +// Before: +use function GuzzleHttp\json_decode; + +$data = json_decode($json); + +// After: +$data = \json_decode($json, false, 512, \JSON_THROW_ON_ERROR); +``` + +| Original Function | Replacement | +|-------------------|-------------| +| `describe_type` | PHP's `get_debug_type` | +| `headers_from_lines` | `Utils::headersFromLines` | +| `debug_resource` | `Utils::debugResource` | +| `choose_handler` | `Utils::chooseHandler` | +| `default_user_agent` | `Utils::defaultUserAgent` | +| `default_ca_bundle` | none; use the system trust store or `verify` | +| `normalize_header_keys` | `Utils::normalizeHeaderKeys` | +| `is_host_in_noproxy` | `ProxyOptions::isHostInNoProxy` | +| `json_decode` | PHP's `json_decode` with `JSON_THROW_ON_ERROR` | +| `json_encode` | PHP's `json_encode` with `JSON_THROW_ON_ERROR` | + +The deprecated `Utils::jsonDecode()` and `Utils::jsonEncode()` methods have +also been removed. Use PHP's native JSON functions with +`JSON_THROW_ON_ERROR`. Failures throw `JsonException` directly. + +See the "Removed Proxy Helper API" section below for `is_host_in_noproxy()` +behavior differences. The deprecated `Utils::defaultCaBundle()` and the +internal `Utils::isUriInNoProxy()` helpers have also been removed; use the +`verify` option and `ProxyOptions::isUriInNoProxy()` respectively. + +#### Removed Middleware Helper APIs + +`RetryMiddleware::exponentialDelay()` has been removed. The retry middleware +continues to use the same exponential backoff calculation by default. This only +affects code that called the static helper directly; inline that calculation or +pass a custom delay callable to `Middleware::retry()`. + +`RedirectMiddleware::$defaultSettings` has been removed. Use +`RedirectMiddleware::DEFAULT_SETTINGS` instead. + +#### Removed Proxy Helper API + +`Utils::isHostInNoProxy()` has been removed. + +Use `ProxyOptions::resolve()` when implementing Guzzle-compatible proxy handling +in a custom handler. Use `ProxyOptions::isUriInNoProxy()` when checking whether +a request URI matches a no-proxy list. Use `ProxyOptions::isHostInNoProxy()` +only when checking a host string directly. The environment-variable fallback +performed by the built-in handlers is not part of `ProxyOptions::resolve()`; +custom handlers that want it must implement their own environment lookup. + +These helpers share the normalized no-proxy matching of the Guzzle 7 `Utils` +helpers: domain matching is case-insensitive and ignores a single trailing DNS +root dot, IP literals are normalized before comparison, and CIDR entries match +IP literal hosts. They differ from the Guzzle 7 helpers in three ways — they +throw `InvalidArgumentException` for non-string list entries where Guzzle 7 +skips them, a leading-dot entry such as `.example.com` also matches the bare +domain, and string no-proxy lists are split on whitespace as well as commas. + +#### Removed Type Description Helper API + +`Utils::describeType()` has been removed. Use PHP's `get_debug_type()` instead. + +#### Non-instantiable Utility Classes + +Static utility and constant classes such as `GuzzleHttp\Middleware`, +`GuzzleHttp\Utils`, `GuzzleHttp\RequestOptions`, +`GuzzleHttp\Handler\HeaderProcessor`, and `GuzzleHttp\Handler\Proxy` now have +private constructors. `GuzzleHttp\Handler\Proxy` is also declared `final`. + +These classes only expose static members. Replace any accidental instantiation +with static method calls or constant access. + +#### Removed HandlerStack String Dump + +`HandlerStack::__toString()` has been removed. + +#### Native PHP Serialization of Runtime Objects + +Runtime objects such as clients, handler stacks, middleware, handlers, pools, +mock handlers, cURL transport objects, and persistent cookie jars no longer +support native PHP `serialize()` or `unserialize()`. Persist configuration or +cookie data explicitly and rebuild runtime objects during bootstrap. + +#### Sensitive Stack Trace Arguments + +Guzzle 8 marks credential-bearing parameters with `SensitiveParameter`. On PHP +8.2 and later, selected exception-trace arguments are represented by +`SensitiveParameterValue` instead of exposing the original argument. PHP 7.4 +through 8.1 do not redact trace arguments. The attribute does not redact logs, +exception messages, HTTP traffic, properties, captured variables, return +values, custom callback frames, or the separate `$this`/`object` entry in +explicit backtraces. PHP 8.2 also does not reliably redact named values +collected by an attributed variadic; PHP 8.3+ does. + 6.0 to 7.0 ---------- @@ -12,7 +1546,7 @@ Please make sure: - You are calling a function or a method with the correct type. - If you extend a class of Guzzle; update all signatures on methods you override. -#### Other backwards compatibility breaking changes +#### Other Backwards Compatibility Breaking Changes - Class `GuzzleHttp\UriTemplate` is removed. - Class `GuzzleHttp\Exception\SeekException` is removed. @@ -27,7 +1561,7 @@ Please make sure: - Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed. Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative. - Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed. -- Request option `exception` is removed. Please use `http_errors`. +- Request option `exceptions` is removed. Please use `http_errors`. - Request option `save_to` is removed. Please use `sink`. - Pool option `pool_size` is removed. Please use `concurrency`. - We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility. @@ -35,7 +1569,7 @@ Please make sure: - The `log` middleware will log the errors with level `error` instead of `notice` - Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher). -#### Native functions calls +#### Native Functions Calls All internal native functions calls of Guzzle are now prefixed with a slash. This change makes it impossible for method overloading by other libraries or applications. @@ -49,7 +1583,7 @@ curl_version(); \curl_version(); ``` -For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..7.0.0). 5.0 to 6.0 ---------- @@ -189,11 +1723,11 @@ $client = new GuzzleHttp\Client(['handler' => $handler]); ## POST Requests -This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params) +This version added the [`form_params`](https://github.com/guzzle/guzzle/blob/6.5/docs/request-options.rst#form_params) and `multipart` request options. `form_params` is an associative array of strings or array of strings and is used to serialize an `application/x-www-form-urlencoded` POST request. The -[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart) +[`multipart`](https://github.com/guzzle/guzzle/blob/6.5/docs/request-options.rst#multipart) option is now used to send a multipart/form-data POST request. `GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add @@ -209,7 +1743,7 @@ The `base_url` option has been renamed to `base_uri`. ## Rewritten Adapter Layer -Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send +Guzzle now uses [RingPHP](https://github.com/guzzle/RingPHP) to send HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor is still supported, but it has now been renamed to `handler`. Instead of passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP @@ -231,7 +1765,7 @@ from the following classes: Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following functions can be used as replacements. -- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode` +- `GuzzleHttp\json_decode` -> PHP's `json_decode` - `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath` - `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` - `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, @@ -575,8 +2109,6 @@ You can intercept a request and inject a response using the `intercept()` event of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and `GuzzleHttp\Event\ErrorEvent` event. -See: http://docs.guzzlephp.org/en/latest/events.html - ## Inflection The `Guzzle\Inflection` namespace has been removed. This is not a core concern @@ -668,9 +2200,9 @@ in separate repositories: The service description layer of Guzzle has moved into two separate packages: -- http://github.com/guzzle/command Provides a high level abstraction over web +- https://github.com/guzzle/command Provides a high level abstraction over web services by representing web service operations using commands. -- http://github.com/guzzle/guzzle-services Provides an implementation of +- https://github.com/guzzle/guzzle-services Provides an implementation of guzzle/command that provides request serialization and response parsing using Guzzle service descriptions. @@ -870,7 +2402,7 @@ HeaderInterface (e.g. toArray(), getAll(), etc.). 3.3 to 3.4 ---------- -Base URLs of a client now follow the rules of https://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs. +Base URLs of a client now follow the rules of https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2 when merging URLs. 3.2 to 3.3 ---------- diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json b/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json index f369ce67e..a3f45936f 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/composer.json @@ -1,6 +1,7 @@ { "name": "guzzlehttp/guzzle", "description": "Guzzle is a PHP HTTP client library", + "license": "MIT", "keywords": [ "framework", "http", @@ -12,7 +13,6 @@ "PSR-7", "PSR-18" ], - "license": "MIT", "authors": [ { "name": "Graham Campbell", @@ -51,28 +51,70 @@ } ], "require": { - "php": "^7.2.5 || ^8.0", + "php": "^7.4 || ^8.0", "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/promises": "^3.1", + "guzzlehttp/psr7": "^3.2", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" + "psr/http-factory": "^1.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "require-dev": { "ext-curl": "*", - "bamarni/composer-bin-plugin": "^1.8.1", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "bamarni/composer-bin-plugin": "^1.8.2", + "guzzle/client-integration-tests": "4.0.1", + "guzzlehttp/test-server": "^1.1", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, + "provide": { + "psr/http-client-implementation": "1.0" + }, "suggest": { "ext-curl": "Required for CURL handler support", "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, + "repositories": [ + { + "type": "package", + "package": { + "name": "guzzle/client-integration-tests", + "version": "v4.0.1", + "require": { + "guzzlehttp/psr7": "^3.2", + "php": "^7.4 || ^8.0", + "php-http/message": "^1.0 || ^2.0", + "phpunit/phpunit": "^9.6.34", + "th3n3rd/cartesian-product": "^0.3" + }, + "autoload": { + "psr-4": { + "Http\\Client\\Tests\\": "src/" + } + }, + "bin": [ + "bin/http_test_server" + ], + "dist": { + "type": "zip", + "url": "https://codeload.github.com/guzzle/client-integration-tests/zip/e54cb9d94e3aa5e125dcd7d01bec22db202444ac" + } + } + } + ], + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + }, "config": { "allow-plugins": { "bamarni/composer-bin-plugin": true @@ -84,22 +126,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "autoload-dev": { - "psr-4": { - "GuzzleHttp\\Tests\\": "tests/" } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Auth/DigestAuth.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Auth/DigestAuth.php new file mode 100644 index 000000000..d3dcbf51c --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Auth/DigestAuth.php @@ -0,0 +1,515 @@ + + */ + private const ALGORITHMS = [ + 'MD5' => ['hash' => 'md5', 'sess' => false, 'rank' => 10, 'header' => 'MD5'], + 'MD5-SESS' => ['hash' => 'md5', 'sess' => true, 'rank' => 11, 'header' => 'MD5-sess'], + 'SHA-256' => ['hash' => 'sha256', 'sess' => false, 'rank' => 20, 'header' => 'SHA-256'], + 'SHA-256-SESS' => ['hash' => 'sha256', 'sess' => true, 'rank' => 21, 'header' => 'SHA-256-sess'], + 'SHA-512-256' => ['hash' => 'sha512/256', 'sess' => false, 'rank' => 30, 'header' => 'SHA-512-256'], + 'SHA-512-256-SESS' => ['hash' => 'sha512/256', 'sess' => true, 'rank' => 31, 'header' => 'SHA-512-256-sess'], + ]; + + /** + * @var array + */ + private const DIGEST_CHALLENGE_PARAMETER_NAMES = [ + 'realm' => true, + 'domain' => true, + 'nonce' => true, + 'opaque' => true, + 'stale' => true, + 'algorithm' => true, + 'qop' => true, + 'charset' => true, + 'userhash' => true, + ]; + + private function __construct() + { + } + + public static function selectChallenge( + #[\SensitiveParameter] + ResponseInterface $response + ): ?DigestChallenge { + $selected = null; + + foreach ($response->getHeader('WWW-Authenticate') as $header) { + foreach (self::parseAuthenticateHeader($header) as $challenge) { + if ($challenge['scheme'] !== 'digest' || $challenge['invalid']) { + continue; + } + + $digest = self::createChallenge($challenge['params']); + if ($digest === null) { + continue; + } + + if ($selected === null || $digest->algorithm['rank'] > $selected->algorithm['rank']) { + $selected = $digest; + } + } + } + + return $selected; + } + + public static function authorizationHeader( + #[\SensitiveParameter] + RequestInterface $request, + DigestChallenge $challenge, + string $username, + #[\SensitiveParameter] + string $password, + string $cnonce, + string $nc = '00000001' + ): ?string { + if ($challenge->algorithm['sess'] && $challenge->qop === null) { + return null; + } + + $uri = $request->getRequestTarget(); + if ($uri === '') { + $uri = '/'; + } + + foreach ([$username, $challenge->realm, $challenge->nonce, $uri, $cnonce] as $value) { + if (!self::isHeaderSafe($value)) { + return null; + } + } + + if ($challenge->opaque !== null && !self::isHeaderSafe($challenge->opaque)) { + return null; + } + + if ($challenge->qop !== null && !self::isNonceCount($nc)) { + return null; + } + + $hash = $challenge->algorithm['hash']; + $ha1 = \hash($hash, $username.':'.$challenge->realm.':'.$password); + + if ($challenge->algorithm['sess']) { + $ha1 = \hash($hash, $ha1.':'.$challenge->nonce.':'.$cnonce); + } + + $ha2 = \hash($hash, $request->getMethod().':'.$uri); + $response = $challenge->qop === null + ? \hash($hash, $ha1.':'.$challenge->nonce.':'.$ha2) + : \hash($hash, $ha1.':'.$challenge->nonce.':'.$nc.':'.$cnonce.':'.$challenge->qop.':'.$ha2); + + if ($challenge->userhash) { + $headerUsername = \hash($hash, $username.':'.$challenge->realm); + if ($headerUsername === false) { + return null; + } + } else { + $headerUsername = $username; + } + + $parts = [ + 'username='.self::quote($headerUsername), + 'realm='.self::quote($challenge->realm), + 'nonce='.self::quote($challenge->nonce), + 'uri='.self::quote($uri), + 'response='.self::quote($response), + 'algorithm='.$challenge->algorithm['header'], + ]; + + if ($challenge->opaque !== null) { + $parts[] = 'opaque='.self::quote($challenge->opaque); + } + + if ($challenge->qop !== null) { + $parts[] = 'qop='.$challenge->qop; + $parts[] = 'nc='.$nc; + $parts[] = 'cnonce='.self::quote($cnonce); + } + + if ($challenge->userhash) { + $parts[] = 'userhash=true'; + } + + return 'Digest '.\implode(', ', $parts); + } + + /** + * @return list, invalid: bool}> + */ + public static function parseAuthenticateHeader(string $header): array + { + $length = \strlen($header); + $offset = 0; + $challenges = []; + + while (true) { + self::skipSeparators($header, $offset, $length); + if ($offset >= $length) { + break; + } + + $scheme = self::readToken($header, $offset, $length); + if ($scheme === null) { + break; + } + + self::skipWhitespace($header, $offset, $length); + + if (self::skipToken68Challenge($header, $offset, $length)) { + $challenges[] = [ + 'scheme' => Psr7\Utils::asciiToLower($scheme), + 'params' => [], + 'invalid' => false, + ]; + + continue; + } + + $params = []; + $invalid = false; + + while ($offset < $length) { + self::skipWhitespace($header, $offset, $length); + + if ($offset < $length && $header[$offset] === ',') { + if (self::commaStartsNextChallenge($header, $offset + 1, $length)) { + if (Psr7\Utils::caselessEquals($scheme, 'Digest') + && self::commaStartsKnownDigestParameterWithoutValue($header, $offset + 1, $length) + ) { + $invalid = true; + break; + } + + ++$offset; + break; + } + + ++$offset; + continue; + } + + $name = self::readToken($header, $offset, $length); + if ($name === null) { + if ($offset < $length) { + $invalid = true; + } + + break; + } + + self::skipWhitespace($header, $offset, $length); + if ($offset >= $length || $header[$offset] !== '=') { + $invalid = true; + break; + } + + ++$offset; + self::skipWhitespace($header, $offset, $length); + + $value = self::readValue($header, $offset, $length); + if ($value === null) { + $invalid = true; + break; + } + + $lowerName = Psr7\Utils::asciiToLower($name); + if (\array_key_exists($lowerName, $params)) { + $invalid = true; + } + + $params[$lowerName] = $value; + + self::skipWhitespace($header, $offset, $length); + if ($offset >= $length) { + break; + } + + if ($header[$offset] !== ',') { + $invalid = true; + break; + } + } + + $challenges[] = [ + 'scheme' => Psr7\Utils::asciiToLower($scheme), + 'params' => $params, + 'invalid' => $invalid, + ]; + } + + return $challenges; + } + + /** + * @param array $params + */ + private static function createChallenge(array $params): ?DigestChallenge + { + if (!isset($params['nonce']) || $params['nonce'] === '') { + return null; + } + + if (isset($params['charset']) && !Psr7\Utils::caselessEquals($params['charset'], 'UTF-8')) { + return null; + } + + $algorithm = self::algorithm($params['algorithm'] ?? null); + if ($algorithm === null) { + return null; + } + + $qop = self::selectQop($params['qop'] ?? null); + if ($qop === false) { + return null; + } + + if ($algorithm['sess'] && $qop === null) { + return null; + } + + $challenge = new DigestChallenge(); + $challenge->algorithm = $algorithm; + $challenge->realm = $params['realm'] ?? ''; + $challenge->nonce = $params['nonce']; + $challenge->opaque = $params['opaque'] ?? null; + if (isset($params['domain'])) { + $domainAreas = \preg_split('/[ \t]+/', $params['domain']); + + if ($domainAreas === false) { + throw new \RuntimeException('Unable to split the Digest domain list: '.\preg_last_error_msg()); + } + + $challenge->domain = \array_values(\array_filter($domainAreas)); + } else { + $challenge->domain = []; + } + $challenge->qop = $qop; + $challenge->stale = isset($params['stale']) && Psr7\Utils::caselessEquals($params['stale'], 'true'); + $challenge->userhash = isset($params['userhash']) && Psr7\Utils::caselessEquals($params['userhash'], 'true'); + + return $challenge; + } + + /** + * @return array{name: string, hash: string, sess: bool, rank: int, header: string}|null + */ + private static function algorithm(?string $algorithm): ?array + { + $name = Psr7\Utils::asciiToUpper($algorithm ?? 'MD5'); + + if (!isset(self::ALGORITHMS[$name])) { + return null; + } + + $mapped = self::ALGORITHMS[$name]; + if (!\in_array($mapped['hash'], \hash_algos(), true)) { + return null; + } + + return ['name' => $name] + $mapped; + } + + /** + * @return string|false|null + */ + private static function selectQop(?string $qop) + { + if ($qop === null) { + return null; + } + + $tokens = \array_map( + static function (string $token): string { + return Psr7\Utils::asciiToLower(\trim($token, " \t")); + }, + \explode(',', $qop) + ); + + return \in_array('auth', $tokens, true) ? 'auth' : false; + } + + private static function quote(string $value): string + { + return '"'.\strtr($value, [ + '\\' => '\\\\', + '"' => '\\"', + ]).'"'; + } + + private static function isHeaderSafe(string $value): bool + { + return \preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value) === 1; + } + + private static function isNonceCount(string $nc): bool + { + // Nonce counts start at one. + return $nc !== '00000000' && \preg_match('/^[0-9a-f]{8}$/D', $nc) === 1; + } + + private static function commaStartsNextChallenge(string $header, int $offset, int $length): bool + { + self::skipWhitespace($header, $offset, $length); + $nextOffset = $offset; + $token = self::readToken($header, $nextOffset, $length); + if ($token === null) { + return false; + } + + self::skipWhitespace($header, $nextOffset, $length); + + return $nextOffset >= $length || $header[$nextOffset] !== '='; + } + + private static function commaStartsKnownDigestParameterWithoutValue(string $header, int $offset, int $length): bool + { + self::skipWhitespace($header, $offset, $length); + $name = self::readToken($header, $offset, $length); + if ($name === null || !isset(self::DIGEST_CHALLENGE_PARAMETER_NAMES[Psr7\Utils::asciiToLower($name)])) { + return false; + } + + self::skipWhitespace($header, $offset, $length); + + return $offset >= $length || $header[$offset] !== '='; + } + + private static function skipToken68Challenge(string $header, int &$offset, int $length): bool + { + $cursor = $offset; + $hasValue = false; + while ($cursor < $length && self::isToken68Char($header[$cursor])) { + $hasValue = true; + ++$cursor; + } + + while ($cursor < $length && $header[$cursor] === '=') { + ++$cursor; + } + + if (!$hasValue) { + return false; + } + + $after = $cursor; + self::skipWhitespace($header, $after, $length); + + if ($after < $length && $header[$after] !== ',') { + return false; + } + + $offset = $after; + + return true; + } + + private static function skipSeparators(string $header, int &$offset, int $length): void + { + while ($offset < $length) { + $char = $header[$offset]; + if ($char !== ',' && !self::isWhitespace($char)) { + return; + } + + ++$offset; + } + } + + private static function skipWhitespace(string $header, int &$offset, int $length): void + { + while ($offset < $length && self::isWhitespace($header[$offset])) { + ++$offset; + } + } + + private static function isWhitespace(string $char): bool + { + return $char === ' ' || $char === "\t"; + } + + private static function readToken(string $header, int &$offset, int $length): ?string + { + $start = $offset; + while ($offset < $length && self::isTokenChar($header[$offset])) { + ++$offset; + } + + if ($offset === $start) { + return null; + } + + $token = \substr($header, $start, $offset - $start); + + return $token === false ? null : $token; + } + + private static function isTokenChar(string $char): bool + { + return \strspn($char, "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") === 1; + } + + private static function isToken68Char(string $char): bool + { + return \strspn($char, '-._~+/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') === 1; + } + + private static function readValue(string $header, int &$offset, int $length): ?string + { + if ($offset >= $length) { + return null; + } + + if ($header[$offset] === '"') { + return self::readQuotedString($header, $offset, $length); + } + + return self::readToken($header, $offset, $length); + } + + private static function readQuotedString(string $header, int &$offset, int $length): ?string + { + ++$offset; + $value = ''; + $escaped = false; + + while ($offset < $length) { + $char = $header[$offset++]; + + if ($escaped) { + $value .= $char; + $escaped = false; + continue; + } + + if ($char === '\\') { + $escaped = true; + continue; + } + + if ($char === '"') { + return $value; + } + + $value .= $char; + } + + return null; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Auth/DigestChallenge.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Auth/DigestChallenge.php new file mode 100644 index 000000000..d03020344 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Auth/DigestChallenge.php @@ -0,0 +1,29 @@ + */ + public array $domain = []; + + public ?string $qop = null; + + public bool $stale = false; + + public bool $userhash = false; +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/AuthMiddleware.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/AuthMiddleware.php new file mode 100644 index 000000000..3d1e8e13a --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/AuthMiddleware.php @@ -0,0 +1,843 @@ + + */ + private const DIGEST_PROBE_PAYLOAD_HEADERS = [ + 'Transfer-Encoding', + 'Expect', + 'Trailer', + 'Content-Range', + 'Content-Encoding', + 'Content-MD5', + 'Digest', + 'Content-Digest', + 'Repr-Digest', + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @var callable(): string + */ + private $cnonceGenerator; + + private bool $reuseChallenges; + + private ?string $credentialHashSecret = null; + + /** + * @var array + */ + private array $digestChallenges = []; + + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler + * @param (callable(): string)|null $cnonceGenerator + */ + public function __construct(callable $nextHandler, ?callable $cnonceGenerator = null, bool $reuseChallenges = true) + { + $this->nextHandler = $nextHandler; + $this->cnonceGenerator = $cnonceGenerator ?? static function (): string { + return \bin2hex(\random_bytes(16)); + }; + $this->reuseChallenges = $reuseChallenges; + } + + /** + * @return PromiseInterface + */ + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { + $auth = $options['auth'] ?? null; + if ($auth === null || $auth === false || $auth === [] || \is_string($auth)) { + return ($this->nextHandler)($request, $options); + } + + $normalizedAuth = self::normalizeAuth($auth); + if ($normalizedAuth === null) { + return ($this->nextHandler)($request, $options); + } + + [$username, $password, $type] = $normalizedAuth; + + if ($type === 'basic') { + return $this->sendBasic($request, $options, $username, $password); + } + + return $this->sendDigest($request, $options, $username, $password); + } + + /** + * @param array $auth + * + * @return array{0: string, 1: string, 2: 'basic'|'digest'}|null + */ + private static function normalizeAuth( + #[\SensitiveParameter] + array $auth + ): ?array { + $type = 'basic'; + if (\array_key_exists(2, $auth) && $auth[2] !== null) { + if (!\is_string($auth[2])) { + throw new InvalidArgumentException('auth type must be a string'); + } + + $type = Psr7\Utils::asciiToLower($auth[2]); + } + + if (!\in_array($type, ['basic', 'digest'], true)) { + return null; + } + + if (!\array_key_exists(0, $auth) || !\array_key_exists(1, $auth)) { + throw new InvalidArgumentException('auth must contain username and password strings'); + } + + if (!\is_string($auth[0]) || !\is_string($auth[1])) { + throw new InvalidArgumentException('auth must contain username and password strings'); + } + + return [$auth[0], $auth[1], $type]; + } + + /** + * @return PromiseInterface + */ + private function sendBasic( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + string $username, + #[\SensitiveParameter] + string $password + ): PromiseInterface { + if (\strpos($username, ':') !== false) { + throw new InvalidArgumentException('Basic authentication username must not contain a colon'); + } + + if (\preg_match('/[\x00-\x1F\x7F]/', $username.$password) !== 0) { + throw new InvalidArgumentException('Basic authentication credentials must not contain ASCII control characters'); + } + + unset($options['auth']); + + return ($this->nextHandler)( + $request->withHeader('Authorization', 'Basic '.\base64_encode($username.':'.$password)), + $options + ); + } + + /** + * @return PromiseInterface + */ + private function sendDigest( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + string $username, + #[\SensitiveParameter] + string $password + ): PromiseInterface { + $preemptive = $this->reuseChallenges + ? $this->preemptiveDigestRequest($request, $username, $password) + : null; + + if ($preemptive !== null) { + $preemptiveOptions = self::withTemporarySink($options); + unset($preemptiveOptions['auth']); + + return ($this->nextHandler)($preemptive, $preemptiveOptions)->then( + function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($request, $options, $preemptiveOptions, $username, $password) { + // Clear on 4xx/5xx other than 401 so non-conformant stale-nonce + // errors cannot poison the cache indefinitely. + $status = $response->getStatusCode(); + if ($status >= 400 && $status !== 401) { + $this->clearDigestChallenge($request); + } + + return $this->handleDigestResponse($request, $options, $preemptiveOptions, $response, $username, $password, false); + }, + function ( + #[\SensitiveParameter] + $reason + ) use ($request, $preemptiveOptions) { + return $this->handleDigestRejection($request, $preemptiveOptions, $reason, true); + } + ); + } + + $probeOptions = self::withTemporarySink($options); + unset($probeOptions['auth']); + + [$probeRequest, $bodyWithheld] = self::probeRequest($request, $options); + + if ($bodyWithheld) { + // The probe has no payload, so prepare_body must not add Expect + // if a custom empty stream has unknown size. + $probeOptions[RequestOptions::EXPECT] = false; + } + + return ($this->nextHandler)($probeRequest, $probeOptions)->then( + function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($request, $options, $probeOptions, $username, $password, $bodyWithheld) { + return $this->handleDigestResponse($request, $options, $probeOptions, $response, $username, $password, $bodyWithheld); + }, + function ( + #[\SensitiveParameter] + $reason + ) use ($request, $probeOptions) { + return $this->handleDigestRejection($request, $probeOptions, $reason, false); + } + ); + } + + private function preemptiveDigestRequest( + #[\SensitiveParameter] + RequestInterface $request, + string $username, + #[\SensitiveParameter] + string $password + ): ?RequestInterface { + try { + if ($request->getBody()->getSize() !== 0) { + return null; + } + } catch (\Exception $e) { + return null; + } + + $key = self::digestCacheKey($request); + if ($key === null) { + return null; + } + + $entry = $this->digestChallenges[$key] ?? null; + if ($entry === null + || $entry['credentials'] !== $this->digestCredentialKey($username, $password) + || !self::challengeCoversRequest($entry['challenge'], $request) + ) { + return null; + } + + $authorization = DigestAuth::authorizationHeader( + $request, + $entry['challenge'], + $username, + $password, + ($this->cnonceGenerator)(), + \sprintf('%08x', $entry['nextNc']) + ); + + if ($authorization === null) { + return null; + } + + ++$this->digestChallenges[$key]['nextNc']; + + return $request->withHeader('Authorization', $authorization); + } + + /** + * Builds the unauthenticated probe request for a Digest handshake. + * + * @return array{0: RequestInterface, 1: bool} + */ + private static function probeRequest( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): array { + $probe = $request->withoutHeader('Authorization'); + + try { + $size = $probe->getBody()->getSize(); + } catch (\Exception $e) { + $size = null; + } + + if ($size === 0) { + return [$probe, false]; + } + + $streamFactory = self::requireStreamFactory( + $options[RequestOptions::STREAM_FACTORY] ?? new HttpFactory() + ); + + $probe = $probe->withBody($streamFactory->createStream('')) + ->withHeader('Content-Length', '0'); + + foreach (self::DIGEST_PROBE_PAYLOAD_HEADERS as $header) { + $probe = $probe->withoutHeader($header); + } + + return [$probe, true]; + } + + /** + * @return ResponseInterface|PromiseInterface + */ + private function handleDigestResponse( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + array $probeOptions, + #[\SensitiveParameter] + ResponseInterface $response, + string $username, + #[\SensitiveParameter] + string $password, + bool $bodyWithheld + ) { + $status = $response->getStatusCode(); + + $previousEntry = null; + if ($this->reuseChallenges && $status === 401) { + $key = self::digestCacheKey($request); + $previousEntry = $key !== null ? ($this->digestChallenges[$key] ?? null) : null; + $this->clearDigestChallenge($request); + } + + if ($status !== 401) { + if ($this->reuseChallenges && $response->hasHeader('Authentication-Info')) { + $this->clearDigestChallenge($request); + } + + $response = self::restoreOriginalSink($request, $response, $probeOptions); + + if ($bodyWithheld && $status !== 407 && !self::isRedirectLikeResponse($response)) { + throw new ResponseException( + 'Digest authentication failed because the server did not issue a challenge; the request was probed without its body', + $request, + $response + ); + } + + return $response; + } + + $challenge = DigestAuth::selectChallenge($response); + if ($challenge === null) { + return self::restoreOriginalSink($request, $response, $probeOptions); + } + + $retries = $options['__guzzle_digest_retries'] ?? 0; + if (!\is_int($retries)) { + $retries = 0; + } + + if (($retries > 0 && !$challenge->stale) || $retries >= self::DIGEST_MAX_RETRIES) { + return self::restoreOriginalSink($request, $response, $probeOptions); + } + + try { + self::rewindBodyForRetry($request, $bodyWithheld); + } catch (\Exception $e) { + $response = self::restoreOriginalSink($request, $response, $probeOptions); + + throw new ResponseException( + 'Digest authentication failed because the request body could not be rewound', + $request, + $response, + $e + ); + } + + $nonceCounts = self::digestNonceCountsFromOptions($options); + + if ($previousEntry !== null + && $previousEntry['credentials'] === $this->digestCredentialKey($username, $password) + ) { + self::rememberDigestNonceCount($nonceCounts, $previousEntry['challenge'], $previousEntry['nextNc']); + } + + $nc = $challenge->qop === null ? 1 : ($nonceCounts[self::digestNonceCountKey($challenge)] ?? 1); + + $authorization = DigestAuth::authorizationHeader( + $request, + $challenge, + $username, + $password, + ($this->cnonceGenerator)(), + \sprintf('%08x', $nc) + ); + + if ($authorization === null) { + return self::restoreOriginalSink($request, $response, $probeOptions); + } + + $response->getBody()->close(); + + self::rememberDigestNonceCount($nonceCounts, $challenge, $nc + 1); + + $retryOptions = $options; + $retryOptions['__guzzle_digest_retries'] = $retries + 1; + $retryOptions[self::DIGEST_NONCE_COUNTS_OPTION] = $nonceCounts; + $downstreamOptions = $retryOptions; + // The nonce-count map only carries handshake state into the retry + // recursion; downstream handlers must not observe it. + unset($downstreamOptions[self::DIGEST_NONCE_COUNTS_OPTION]); + // The caller's delay applies once, before the first Digest leg, not + // before each handshake retry. + unset($downstreamOptions[RequestOptions::DELAY]); + $downstreamOptions = self::withTemporarySink($downstreamOptions); + unset($downstreamOptions['auth']); + + $retryRequest = $request->withHeader('Authorization', $authorization); + + return ($this->nextHandler)($retryRequest, $downstreamOptions)->then( + function ( + #[\SensitiveParameter] + ResponseInterface $retryResponse + ) use ($retryRequest, $retryOptions, $downstreamOptions, $username, $password, $challenge, $nc) { + if ($this->reuseChallenges && self::isCacheableDigestSuccess($retryResponse)) { + $this->storeDigestChallenge($retryRequest, $challenge, $username, $password, $nc + 1); + } + + return $this->handleDigestResponse($retryRequest, $retryOptions, $downstreamOptions, $retryResponse, $username, $password, false); + }, + function ( + #[\SensitiveParameter] + $reason + ) use ($retryRequest, $downstreamOptions) { + return $this->handleDigestRejection($retryRequest, $downstreamOptions, $reason, false); + } + ); + } + + private static function isCacheableDigestSuccess(ResponseInterface $response): bool + { + $status = $response->getStatusCode(); + + return $status >= 200 && $status < 400 && !$response->hasHeader('Authentication-Info'); + } + + private function storeDigestChallenge( + #[\SensitiveParameter] + RequestInterface $request, + DigestChallenge $challenge, + string $username, + #[\SensitiveParameter] + string $password, + int $nextNc + ): void { + $key = self::digestCacheKey($request); + if ($key === null || $challenge->qop === null) { + return; + } + + unset($this->digestChallenges[$key]); + + if (\count($this->digestChallenges) >= self::CHALLENGE_CACHE_LIMIT) { + \array_shift($this->digestChallenges); + } + + $this->digestChallenges[$key] = [ + 'challenge' => $challenge, + 'credentials' => $this->digestCredentialKey($username, $password), + 'nextNc' => $nextNc, + ]; + } + + private function clearDigestChallenge(RequestInterface $request): void + { + $key = self::digestCacheKey($request); + if ($key !== null) { + unset($this->digestChallenges[$key]); + } + } + + /** + * @param array $options + * + * @return array + */ + private static function digestNonceCountsFromOptions(array $options): array + { + $counts = $options[self::DIGEST_NONCE_COUNTS_OPTION] ?? []; + if (!\is_array($counts)) { + return []; + } + + $normalized = []; + foreach ($counts as $key => $nextNc) { + if (\is_string($key) && \is_int($nextNc) && $nextNc > 0 && $nextNc < \PHP_INT_MAX) { + $normalized[$key] = $nextNc; + } + } + + return $normalized; + } + + private static function digestNonceCountKey(DigestChallenge $challenge): string + { + return \implode("\0", [ + $challenge->realm, + $challenge->nonce, + $challenge->qop ?? '', + $challenge->algorithm['header'], + ]); + } + + /** + * @param array $counts + */ + private static function rememberDigestNonceCount(array &$counts, DigestChallenge $challenge, int $nextNc): void + { + if ($challenge->qop === null) { + return; + } + + $key = self::digestNonceCountKey($challenge); + $counts[$key] = \max($counts[$key] ?? 1, $nextNc); + } + + /** + * @param mixed $reason + * + * @return PromiseInterface + */ + private function handleDigestRejection( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + $reason, + bool $preemptive + ): PromiseInterface { + if ($this->reuseChallenges && $reason instanceof ResponseException) { + $response = $reason->getResponse(); + + if ($response->getStatusCode() === 401 + || $response->hasHeader('Authentication-Info') + || ($preemptive && $response->getStatusCode() >= 400) + ) { + $this->clearDigestChallenge($request); + } + } + + return self::restoreOriginalSinkOnRejection($options, $reason); + } + + private static function digestCacheKey(RequestInterface $request): ?string + { + $uri = $request->getUri(); + $scheme = Psr7\Utils::asciiToLower($uri->getScheme()); + $host = HostIdentity::canonicalHost($uri->getHost()); + + if (($scheme !== 'http' && $scheme !== 'https') || $host === '') { + return null; + } + + $port = $uri->getPort() ?? ($scheme === 'https' ? 443 : 80); + + return $scheme.'://'.$host.':'.$port.'|'.HostIdentity::canonicalHostHeader($request->getHeaderLine('Host')); + } + + private function digestCredentialKey( + string $username, + #[\SensitiveParameter] + string $password + ): string { + if ($this->credentialHashSecret === null) { + $this->credentialHashSecret = \random_bytes(32); + } + + return \hash_hmac('sha256', $username."\0".$password, $this->credentialHashSecret); + } + + private static function challengeCoversRequest(DigestChallenge $challenge, RequestInterface $request): bool + { + if ($challenge->domain === []) { + return true; + } + + $target = $request->getRequestTarget(); + foreach ($challenge->domain as $space) { + $prefix = self::protectionSpacePath($space, $request); + if ($prefix !== null && \str_starts_with($target, $prefix)) { + return true; + } + } + + return false; + } + + private static function protectionSpacePath(string $space, RequestInterface $request): ?string + { + if ($space === '') { + return null; + } + + if ($space[0] === '/') { + return $space; + } + + try { + $uri = new Psr7\Uri($space); + } catch (\InvalidArgumentException $e) { + return null; + } + + if (Psr7\UriComparator::isCrossOrigin($uri, self::effectiveRequestUri($request))) { + return null; + } + + $path = $uri->getPath(); + $prefix = $path === '' ? '/' : $path; + + if ($uri->getQuery() !== '') { + $prefix .= '?'.$uri->getQuery(); + } + + return $prefix; + } + + private static function effectiveRequestUri(RequestInterface $request): UriInterface + { + $uri = $request->getUri(); + $host = $request->getHeaderLine('Host'); + if ($host === '' || Psr7\Utils::caselessEquals($host, $uri->getHost())) { + return $uri; + } + + try { + $authority = new Psr7\Uri('//'.$host); + if ($authority->getHost() === '') { + return $uri; + } + + return $uri->withHost($authority->getHost())->withPort($authority->getPort()); + } catch (\InvalidArgumentException $e) { + return $uri; + } + } + + private static function isRedirectLikeResponse(ResponseInterface $response): bool + { + return $response->hasHeader('Location') + && \in_array($response->getStatusCode(), [301, 302, 303, 307, 308], true); + } + + private static function rewindBodyForRetry( + #[\SensitiveParameter] + RequestInterface $request, + bool $bodyWithheld + ): void { + $body = $request->getBody(); + + if (!$bodyWithheld) { + try { + if ($body->getSize() === 0) { + return; + } + } catch (\Exception $e) { + // Fall through to the standard rewind path. + } + + Psr7\Message::rewindBody($request); + + return; + } + + if ($body->isSeekable()) { + $body->rewind(); + } + } + + private static function withTemporarySink( + #[\SensitiveParameter] + array $options + ): array { + // The 'stream' option is deliberately not checked here: the cURL + // handlers do not support streaming and write each leg to the sink, + // so a configured sink always needs challenge-body protection. + if (!isset($options[RequestOptions::SINK])) { + return $options; + } + + $streamFactory = self::requireStreamFactory( + $options[RequestOptions::STREAM_FACTORY] ?? new HttpFactory() + ); + + $options['__guzzle_auth_original_sink'] = $options[RequestOptions::SINK]; + $options[RequestOptions::SINK] = $streamFactory->createStreamFromResource( + Psr7\Utils::tryFopen('php://temp', 'w+') + ); + + return $options; + } + + private static function restoreOriginalSink( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + ResponseInterface $response, + #[\SensitiveParameter] + array $options + ): ResponseInterface { + if (!\array_key_exists('__guzzle_auth_original_sink', $options)) { + return $response; + } + + try { + $source = $response->getBody(); + if ($source->isSeekable()) { + $source->rewind(); + } + + $target = self::streamForOriginalSink($options['__guzzle_auth_original_sink']); + + Psr7\Utils::copyToStream($source, $target); + + if ($target->isSeekable()) { + $target->rewind(); + } + + return $response->withBody($target); + } catch (\Exception $e) { + throw new ResponseException( + $e->getMessage() !== '' ? $e->getMessage() : 'Failed to write the response body', + $request, + $response, + $e + ); + } + } + + /** + * @param mixed $reason + * + * @return PromiseInterface + */ + private static function restoreOriginalSinkOnRejection( + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + $reason + ): PromiseInterface { + if (!$reason instanceof ResponseException || !\array_key_exists('__guzzle_auth_original_sink', $options)) { + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); + } + + try { + $response = self::restoreOriginalSink($reason->getRequest(), $reason->getResponse(), $options); + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason->withResponse($response)); + } catch (\Throwable $e) { + /** @var PromiseInterface */ + return P\Create::rejectionFor($e); + } + } + + /** + * @param mixed $sink + */ + private static function streamForOriginalSink($sink): StreamInterface + { + if (\is_string($sink)) { + return new LazyOpenStream($sink, 'w+'); + } + + if (\is_resource($sink)) { + return self::streamForResourceSink(Psr7\Utils::streamFor($sink)); + } + + if (!$sink instanceof StreamInterface) { + throw new InvalidArgumentException(\sprintf( + 'sink must be a resource, string, or %s', + StreamInterface::class + )); + } + + return Psr7\Utils::streamFor($sink); + } + + /** + * Decorates a caller-owned sink stream so that closing the response body + * detaches Guzzle's wrapper without closing the original PHP resource. + */ + private static function streamForResourceSink(StreamInterface $stream): StreamInterface + { + return Psr7\FnStream::decorate($stream, [ + 'close' => static function () use ($stream): void { + $stream->detach(); + }, + ]); + } + + /** + * @param mixed $factory + */ + private static function requireStreamFactory($factory): StreamFactoryInterface + { + if (!$factory instanceof StreamFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::STREAM_FACTORY, + StreamFactoryInterface::class + )); + } + + return $factory; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php index 6eca94ef9..8912d3384 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizer.php @@ -1,17 +1,16 @@ truncateAt = $truncateAt; } @@ -21,8 +20,10 @@ final class BodySummarizer implements BodySummarizerInterface */ public function summarize(MessageInterface $message): ?string { - return $this->truncateAt === null - ? \GuzzleHttp\Psr7\Message::bodySummary($message) - : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + try { + return Psr7\Message::bodySummary($message, $this->truncateAt); + } catch (\Exception $e) { + return null; + } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php index 3e02e036e..9d23d9aad 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -1,5 +1,7 @@ true) and does not limit overlapping + * buffered calls. + * - max_total_connections: (int|null) Maximum concurrent connections + * overall, enforced by the default CurlMultiHandler. The default stream + * fallback receives the cap as a marker only: it rejects enabled + * response streaming ("stream" => true) and does not limit overlapping + * buffered calls. + * - multiplex: (string|null) Multiplexing::NONE to disable multiplexing on + * the default CurlMultiHandler; the value also becomes the default + * "multiplex" request option. Other Multiplexing::* values act as the + * default request option only. * - **: any request option * - * @param array $config Client configuration settings. + * @param array{ + * handler?: callable(RequestInterface, array): PromiseInterface, + * base_uri?: string|UriInterface, + * transport_sharing?: string|null, + * max_host_connections?: int|null, + * max_total_connections?: int|null, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: bool|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $config Client configuration settings and default request options. * - * @see \GuzzleHttp\RequestOptions for a list of available request options. + * @see RequestOptions for a list of available request options. */ - public function __construct(array $config = []) - { - if (!isset($config['handler'])) { - $config['handler'] = HandlerStack::create(); - } elseif (!\is_callable($config['handler'])) { - throw new InvalidArgumentException('handler must be a callable'); + public function __construct( + #[\SensitiveParameter] + array $config = [] + ) { + $handlerOptions = []; + foreach (['max_host_connections', 'max_total_connections'] as $capOption) { + if (\array_key_exists($capOption, $config)) { + if ($config[$capOption] !== null) { + $handlerOptions[$capOption] = $config[$capOption]; + } + + unset($config[$capOption]); + } } - // Convert the base_uri to a UriInterface + // Deliberately not unset: the value also becomes the default + // "multiplex" request option, which the configured handler accepts. + $handlerMultiplex = ($config['multiplex'] ?? null) === Multiplexing::NONE; + + $transportSharing = \array_key_exists('transport_sharing', $config) ? $config['transport_sharing'] : null; + $transportSharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing'); + unset($config['transport_sharing']); + + if (!isset($config['handler'])) { + if ($transportSharingMode !== TransportSharing::NONE) { + $handlerOptions['transport_sharing'] = $transportSharingMode; + } + + if ($handlerMultiplex) { + $handlerOptions['multiplex'] = Multiplexing::NONE; + } + + $config['handler'] = $handlerOptions === [] + ? HandlerStack::create() + : HandlerStack::create(Utils::chooseHandler($handlerOptions)); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } elseif ($handlerOptions !== []) { + throw new InvalidArgumentException('The "max_host_connections" and "max_total_connections" client options require Guzzle to create the default handler. Configure the options on the CurlMultiHandler constructor for numeric enforcement, or on the StreamHandler constructor to reject enabled response streaming, when providing a custom handler.'); + } elseif ($transportSharingMode === TransportSharing::HANDLER_REQUIRE) { + throw new InvalidArgumentException('The "transport_sharing" client option can only require sharing when Guzzle creates the default handler. Configure the "transport_sharing" option on CurlHandler, CurlMultiHandler, or StreamHandler when providing a custom handler.'); + } elseif ($transportSharingMode === TransportSharing::PERSISTENT_REQUIRE) { + throw new InvalidArgumentException('The "transport_sharing" client option can only require sharing when Guzzle creates the default handler. Configure the "transport_sharing" option on CurlHandler or CurlMultiHandler when providing a custom handler.'); + } + + $factory = new HttpFactory(); + + if (!isset($config[RequestOptions::REQUEST_FACTORY])) { + $config[RequestOptions::REQUEST_FACTORY] = $factory; + } + + if (!isset($config[RequestOptions::URI_FACTORY])) { + $config[RequestOptions::URI_FACTORY] = $factory; + } + + if (!isset($config[RequestOptions::STREAM_FACTORY])) { + $config[RequestOptions::STREAM_FACTORY] = $factory; + } + + if (!isset($config[RequestOptions::RESPONSE_FACTORY])) { + $config[RequestOptions::RESPONSE_FACTORY] = $factory; + } + + self::requireRequestFactory($config[RequestOptions::REQUEST_FACTORY]); + self::requireResponseFactory($config[RequestOptions::RESPONSE_FACTORY]); + self::requireStreamFactory($config[RequestOptions::STREAM_FACTORY]); + $uriFactory = self::requireUriFactory($config[RequestOptions::URI_FACTORY]); + + // Convert the base_uri to a UriInterface using the configured URI factory. if (isset($config['base_uri'])) { - $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + $config['base_uri'] = self::createUri($config['base_uri'], $uriFactory); } $this->configureDefaults($config); } - /** - * @param string $method - * @param array $args - * - * @return PromiseInterface|ResponseInterface - * - * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. - */ - public function __call($method, $args) - { - if (\count($args) < 1) { - throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); - } - - $uri = $args[0]; - $opts = $args[1] ?? []; - - return \substr($method, -5) === 'Async' - ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts) - : $this->request($method, $uri, $opts); - } - /** * Asynchronously send an HTTP request. * - * @param array $options Request options to apply to the given - * request and to the transfer. See \GuzzleHttp\RequestOptions. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply to the given request and to the transfer. See {@see RequestOptions}. + * + * @return PromiseInterface */ - public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface - { + public function sendAsync( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { // Merge the base URI into the request URI if needed. $options = $this->prepareDefaults($options); return $this->transfer( - $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), + $request->withUri($this->buildUri($request->getUri(), $options), self::shouldPreserveHost($request)), $options ); } @@ -112,24 +334,103 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface /** * Send an HTTP request. * - * @param array $options Request options to apply to the given - * request and to the transfer. See \GuzzleHttp\RequestOptions. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply to the given request and to the transfer. See {@see RequestOptions}. * * @throws GuzzleException */ - public function send(RequestInterface $request, array $options = []): ResponseInterface - { + public function send( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); } /** - * The HttpClient PSR (PSR-18) specify this method. + * The HttpClient PSR (PSR-18) specifies this method. * - * @inheritDoc + * {@inheritDoc} */ - public function sendRequest(RequestInterface $request): ResponseInterface - { + public function sendRequest( + #[\SensitiveParameter] + RequestInterface $request + ): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; $options[RequestOptions::ALLOW_REDIRECTS] = false; $options[RequestOptions::HTTP_ERRORS] = false; @@ -142,28 +443,120 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string $method HTTP method - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. See {@see RequestOptions}. + * + * @return PromiseInterface */ - public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface - { + public function requestAsync( + string $method, + $uri = '', + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { $options = $this->prepareDefaults($options); - // Remove request modifying parameter because it can be done up-front. - $headers = $options['headers'] ?? []; - $body = $options['body'] ?? null; - $version = $options['version'] ?? '1.1'; - // Merge the URI into the base URI. - $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); - if (\is_array($body)) { + + $version = self::normalizeProtocolVersion($options['version'] ?? '1.1'); + unset($options['version']); + + if (isset($options['body']) && \is_array($options['body'])) { throw $this->invalidBody(); } - $request = new Psr7\Request($method, $uri, $headers, $body, $version); - // Remove the option so that they are not doubly-applied. - unset($options['headers'], $options['body'], $options['version']); + + $uriFactory = isset($options[RequestOptions::URI_FACTORY]) + ? self::requireUriFactory($options[RequestOptions::URI_FACTORY]) + : new HttpFactory(); + $requestFactory = isset($options[RequestOptions::REQUEST_FACTORY]) + ? self::requireRequestFactory($options[RequestOptions::REQUEST_FACTORY]) + : new HttpFactory(); + + // Merge the URI into the base URI. + $uriIsString = \is_string($uri); + $uri = self::createUri($uri, $uriFactory); + $builtUri = $this->buildUri($uri, $options); + if ($uriIsString && $builtUri !== $uri) { + $builtUri = self::createUri((string) $builtUri, $uriFactory); + } + + $uri = $builtUri; + $request = $requestFactory->createRequest($method, $uri); + $request = Psr7\Utils::modifyRequest($request, ['version' => $version]); return $this->transfer($request, $options); } @@ -175,15 +568,93 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string $method HTTP method. - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. See {@see RequestOptions}. * * @throws GuzzleException */ - public function request(string $method, $uri = '', array $options = []): ResponseInterface - { + public function request( + string $method, + $uri = '', + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); } @@ -191,14 +662,12 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface * Get a client configuration option. * * These options include default request options of the client, a "handler" - * (if utilized by the concrete client), and a "base_uri" if utilized by - * the concrete client. + * (if utilized by the concrete client), and a "base_uri" if utilized by the + * concrete client. * * @param string|null $option The config option to retrieve. * - * @return mixed - * - * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + * @return ($option is null ? array : mixed) */ public function getConfig(?string $option = null) { @@ -207,32 +676,186 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface : ($this->config[$option] ?? null); } - private function buildUri(UriInterface $uri, array $config): UriInterface - { + private function buildUri( + UriInterface $uri, + #[\SensitiveParameter] + array $config + ): UriInterface { if (isset($config['base_uri'])) { - $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + $uriFactory = self::requireUriFactory($config[RequestOptions::URI_FACTORY] ?? new HttpFactory()); + $uri = Psr7\UriResolver::resolve(self::createUri($config['base_uri'], $uriFactory), $uri); } - if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { - $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion']; - $uri = Utils::idnUriConvert($uri, $idnOptions); + $idnOptions = Idn::normalizeConversionOption($config['idn_conversion'] ?? null); + if ($idnOptions !== null) { + $uri = Idn::convertUri($uri, $idnOptions); } - return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + if ($uri->getScheme() === '' && $uri->getHost() !== '') { + $uri = $uri->withScheme('http'); + } + + return $uri; + } + + /** + * Whether to preserve an existing Host header when the URI changes. + * + * A header matching the current URI carries no explicit override and is + * regenerated after base URI resolution or IDN conversion. Other values + * are preserved as deliberate overrides, as PSR-7 requires. + */ + private static function shouldPreserveHost(RequestInterface $request): bool + { + if (!$request->hasHeader('Host')) { + return false; + } + + $uri = $request->getUri(); + $host = $uri->getHost(); + $port = $uri->getPort(); + + if ($port !== null) { + $host .= ':'.$port; + } + + return $host !== $request->getHeaderLine('Host'); + } + + /** + * @param mixed $factory + */ + private static function requireRequestFactory($factory): RequestFactoryInterface + { + if (!$factory instanceof RequestFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::REQUEST_FACTORY, + RequestFactoryInterface::class + )); + } + + return $factory; + } + + /** + * @param mixed $factory + */ + private static function requireResponseFactory($factory): ResponseFactoryInterface + { + if (!$factory instanceof ResponseFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::RESPONSE_FACTORY, + ResponseFactoryInterface::class + )); + } + + return $factory; + } + + /** + * @param mixed $factory + */ + private static function requireUriFactory($factory): UriFactoryInterface + { + if (!$factory instanceof UriFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::URI_FACTORY, + UriFactoryInterface::class + )); + } + + return $factory; + } + + /** + * @param mixed $factory + */ + private static function requireStreamFactory($factory): StreamFactoryInterface + { + if (!$factory instanceof StreamFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::STREAM_FACTORY, + StreamFactoryInterface::class + )); + } + + return $factory; + } + + /** + * @param mixed $uri + */ + private static function createUri($uri, UriFactoryInterface $uriFactory): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (\is_string($uri)) { + return $uriFactory->createUri($uri); + } + + throw new InvalidArgumentException(\sprintf('URI must be a string or %s', UriInterface::class)); + } + + /** + * @param mixed $body + */ + private static function createBodyStream($body, StreamFactoryInterface $streamFactory): StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + + if (\is_resource($body)) { + return $streamFactory->createStreamFromResource($body); + } + + if ($body === null) { + return $streamFactory->createStream(); + } + + if (\is_string($body)) { + return $streamFactory->createStream($body); + } + + if ($body instanceof \Iterator) { + return Psr7\Utils::streamFor($body); + } + + if (\is_object($body) && \method_exists($body, '__toString')) { + return $streamFactory->createStream((string) $body); + } + + if (\is_callable($body)) { + return Psr7\Utils::streamFor($body); + } + + throw new InvalidArgumentException(\sprintf( + 'Passing %s to request option "body" is invalid; expected resource|string|null|StreamInterface|callable&object|Iterator|Stringable.', + \get_debug_type($body) + )); } /** * Configures the default options for a client. */ - private function configureDefaults(array $config): void - { + private function configureDefaults( + #[\SensitiveParameter] + array $config + ): void { $defaults = [ - 'allow_redirects' => RedirectMiddleware::$defaultSettings, - 'http_errors' => true, - 'decode_content' => true, - 'verify' => true, - 'cookies' => false, - 'idn_conversion' => false, + 'allow_redirects' => RedirectMiddleware::DEFAULT_SETTINGS, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + 'protocols' => ['http', 'https'], ]; // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. @@ -240,17 +863,20 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface // We can only trust the HTTP_PROXY environment variable in a CLI // process due to the fact that PHP has no reliable mechanism to // get environment variables that start with "HTTP_". - if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + if (\PHP_SAPI === 'cli' && ($proxy = Env::get('HTTP_PROXY'))) { $defaults['proxy']['http'] = $proxy; } - if ($proxy = Utils::getenv('HTTPS_PROXY')) { + if ($proxy = Env::get('HTTPS_PROXY')) { $defaults['proxy']['https'] = $proxy; } - if ($noProxy = Utils::getenv('NO_PROXY')) { - $cleanedNoProxy = \str_replace(' ', '', $noProxy); - $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + $noProxy = Env::get('NO_PROXY'); + if ($noProxy !== null) { + $noProxy = ProxyOptions::normalizeNoProxy($noProxy); + if ($noProxy !== []) { + $defaults['proxy']['no'] = $noProxy; + } } $this->config = $config + $defaults; @@ -263,13 +889,20 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface if (!isset($this->config['headers'])) { $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; } else { + self::assertHeaderOptionTypes($this->config['headers']); + // Add the User-Agent header if one was not already set. + $hasUserAgent = false; foreach (\array_keys($this->config['headers']) as $name) { - if (\strtolower($name) === 'user-agent') { - return; + if (Psr7\Utils::asciiToLower((string) $name) === 'user-agent') { + $hasUserAgent = true; + break; } } - $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + + if (!$hasUserAgent) { + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } } } @@ -278,8 +911,14 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface * * @param array $options Options to modify by reference */ - private function prepareDefaults(array $options): array - { + private function prepareDefaults( + #[\SensitiveParameter] + array $options + ): array { + if (isset($options['handler'])) { + throw new InvalidArgumentException('The "handler" request option is not supported; configure the handler when creating the client, or use a separate client instance for requests that need a different handler.'); + } + $defaults = $this->config; if (!empty($defaults['headers'])) { @@ -310,26 +949,571 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface } } + self::assertRequestOptionTypes($result); + return $result; } + private static function assertRequestOptionTypes( + #[\SensitiveParameter] + array $options + ): void { + if (isset($options['allow_redirects'])) { + if (!\is_bool($options['allow_redirects']) && !\is_array($options['allow_redirects'])) { + self::invalidRequestOptionType('allow_redirects', 'bool|array', $options['allow_redirects']); + } + + if (\is_array($options['allow_redirects'])) { + self::assertAllowRedirectsOptionTypes($options['allow_redirects']); + } + } + + if ( + isset($options['auth']) + && $options['auth'] !== false + && !\is_string($options['auth']) + && !\is_array($options['auth']) + ) { + self::invalidRequestOptionType('auth', 'array{0: string, 1: string, 2?: string|null}|string|false|null', $options['auth']); + } + + if (isset($options['auth']) && \is_array($options['auth']) && $options['auth'] !== []) { + self::assertAuthOptionTypes($options['auth']); + } + + self::assertTlsFileOptionTypes($options, 'cert'); + self::assertIfPresentAndNotString($options, 'cert_type'); + self::assertIfPresentAndNotNumber($options, 'connect_timeout'); + self::assertIfPresentAndNotInt($options, 'crypto_method'); + self::assertIfPresentAndNotInt($options, 'crypto_method_max'); + self::assertIfPresentAndNotBoolOrResource($options, 'debug'); + self::assertIfPresentAndNotBoolOrString($options, 'decode_content'); + self::assertIfPresentAndNotFiniteNonNegativeNumber($options, 'delay'); + self::assertIfPresentAndNotBoolOrInt($options, 'expect'); + + if (isset($options['form_params'])) { + self::assertFormParamTypes($options['form_params']); + } + + self::assertIfPresentAndNotForceIpResolve($options); + + if (isset($options['headers'])) { + self::assertHeaderOptionTypes($options['headers']); + } + + self::assertIfPresentAndNotBool($options, 'http_errors'); + + if (isset($options['multipart'])) { + self::assertMultipartOptionTypes($options['multipart']); + } + + self::assertValidMultiplex($options); + self::assertIfPresentAndNotCallable($options, 'on_headers'); + self::assertIfPresentAndNotCallable($options, 'on_stats'); + self::assertIfPresentAndNotCallable($options, 'on_trailers'); + self::assertIfPresentAndNotCallable($options, 'progress'); + self::assertIfPresentAndNotProtocolArray($options, 'protocols'); + self::assertProxyOptionTypes($options); + self::assertIfPresentAndNotNumber($options, 'read_timeout'); + self::assertIfPresentAndNotInt($options, 'retries'); + + if (isset($options['sink']) && !\is_resource($options['sink']) && !\is_string($options['sink']) && !$options['sink'] instanceof StreamInterface) { + self::invalidRequestOptionType('sink', 'resource|string|StreamInterface', $options['sink']); + } + + self::assertTlsFileOptionTypes($options, 'ssl_key'); + self::assertIfPresentAndNotString($options, 'ssl_key_type'); + self::assertIfPresentAndNotBool($options, 'stream'); + self::assertIfPresentAndNotArray($options, 'stream_context', 'array'); + self::assertIfPresentAndNotBool($options, 'synchronous'); + self::assertIfPresentAndNotNumber($options, 'timeout'); + self::assertIfPresentAndNotBoolOrString($options, 'verify'); + self::assertIfPresentAndNotStringOrNumber($options, 'version'); + self::assertIfPresentAndNotArray($options, 'curl', 'array'); + + if (isset($options['cookies']) && $options['cookies'] !== false && !$options['cookies'] instanceof CookieJarInterface) { + self::invalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies']); + } + } + + private static function assertAllowRedirectsOptionTypes(array $allowRedirects): void + { + self::assertIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max'); + self::assertIfPresentAndNotBool($allowRedirects, 'strict', 'allow_redirects.strict'); + self::assertIfPresentAndNotBool($allowRedirects, 'referer', 'allow_redirects.referer'); + self::assertIfPresentAndNotProtocolArray($allowRedirects, 'protocols', 'allow_redirects.protocols'); + self::assertIfPresentAndNotCallable($allowRedirects, 'on_redirect', 'allow_redirects.on_redirect'); + self::assertIfPresentAndNotBool($allowRedirects, 'track_redirects', 'allow_redirects.track_redirects'); + } + + /** + * @param array $auth + */ + private static function assertAuthOptionTypes( + #[\SensitiveParameter] + array $auth + ): void { + if (!\array_key_exists(0, $auth) || !\is_string($auth[0])) { + self::invalidRequestOptionType('auth.0', 'string', $auth[0] ?? null); + } + + if (!\array_key_exists(1, $auth) || !\is_string($auth[1])) { + self::invalidRequestOptionType('auth.1', 'string', $auth[1] ?? null); + } + + if (\array_key_exists(2, $auth) && $auth[2] !== null && !\is_string($auth[2])) { + self::invalidRequestOptionType('auth.2', 'string|null', $auth[2]); + } + } + + /** + * @param mixed $value + */ + private static function assertFormParamTypes($value): void + { + if (!\is_array($value)) { + self::invalidRequestOptionType('form_params', 'array', $value); + + return; + } + + self::assertFormParamArray($value, 'form_params'); + self::assertFiniteFloats($value, 'form_params'); + } + + private static function assertFormParamArray(array $values, string $path): bool + { + foreach ($values as $key => $item) { + $itemPath = $path.'.'.(string) $key; + if (\is_array($item)) { + if (!self::assertFormParamArray($item, $itemPath)) { + return false; + } + + continue; + } + + if ($item !== null && !\is_scalar($item)) { + self::invalidRequestOptionType($itemPath, 'string|int|float|bool|null|array', $item); + + return false; + } + } + + return true; + } + + private static function assertFiniteFloats(array $values, string $option): void + { + foreach ($values as $key => $value) { + if (\is_array($value)) { + self::assertFiniteFloats($value, $option.'.'.(string) $key); + } elseif (\is_float($value) && !\is_finite($value)) { + throw new InvalidArgumentException(\sprintf( + 'Passing a non-finite float to request option "%s" is invalid; non-finite floats are not supported.', + DiagnosticValue::escape($option.'.'.(string) $key) + )); + } + } + } + + /** + * @param mixed $headers + */ + private static function assertHeaderOptionTypes( + #[\SensitiveParameter] + $headers + ): void { + if (!\is_array($headers)) { + self::invalidRequestOptionType('headers', 'array>|null', $headers); + + return; + } + + foreach ($headers as $name => $value) { + $path = 'headers.'.(string) $name; + if (\is_array($value)) { + if ($value === []) { + self::invalidRequestOptionType($path, 'string|non-empty-array', $value); + } + + foreach ($value as $index => $item) { + if (!\is_string($item)) { + self::invalidRequestOptionType($path.'.'.(string) $index, 'string', $item); + } + } + } elseif (!\is_string($value)) { + self::invalidRequestOptionType($path, 'string|non-empty-array', $value); + } + } + } + + /** + * @param mixed $multipart + */ + private static function assertMultipartOptionTypes($multipart): void + { + if (!\is_array($multipart)) { + self::invalidRequestOptionType('multipart', 'array, filename?: string}>', $multipart); + + return; + } + + foreach ($multipart as $index => $part) { + $path = 'multipart.'.(string) $index; + if (!\is_array($part)) { + self::invalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array, filename?: string}', $part); + + return; + } + + if (!\array_key_exists('name', $part) || (!\is_string($part['name']) && !\is_int($part['name']))) { + self::invalidRequestOptionType($path.'.name', 'string|int', $part['name'] ?? null); + } + + if (!\array_key_exists('contents', $part)) { + self::invalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array, filename?: string}', $part); + } + + if (\array_key_exists('headers', $part)) { + if (!\is_array($part['headers'])) { + self::invalidRequestOptionType($path.'.headers', 'array', $part['headers']); + + return; + } + + foreach ($part['headers'] as $name => $value) { + if (!\is_string($value)) { + self::invalidRequestOptionType($path.'.headers.'.(string) $name, 'string', $value); + } + } + } + + if (\array_key_exists('filename', $part) && !\is_string($part['filename'])) { + self::invalidRequestOptionType($path.'.filename', 'string', $part['filename']); + } + } + } + + private static function assertProxyOptionTypes( + #[\SensitiveParameter] + array $options + ): void { + if (!isset($options['proxy'])) { + return; + } + + if (!\is_string($options['proxy']) && !\is_array($options['proxy'])) { + self::invalidRequestOptionType('proxy', 'string|array{http?: string|null, https?: string|null, no?: string|array|null}', $options['proxy']); + + return; + } + + if (!\is_array($options['proxy'])) { + return; + } + + foreach (['http', 'https'] as $scheme) { + if (\array_key_exists($scheme, $options['proxy']) && $options['proxy'][$scheme] !== null && !\is_string($options['proxy'][$scheme])) { + self::invalidRequestOptionType('proxy.'.$scheme, 'string|null', $options['proxy'][$scheme]); + } + } + + if (!\array_key_exists('no', $options['proxy']) || $options['proxy']['no'] === null) { + return; + } + + if (\is_string($options['proxy']['no'])) { + return; + } + + if (!\is_array($options['proxy']['no'])) { + self::invalidRequestOptionType('proxy.no', 'string|array|null', $options['proxy']['no']); + + return; + } + + foreach ($options['proxy']['no'] as $index => $noProxy) { + if (!\is_string($noProxy)) { + self::invalidRequestOptionType('proxy.no.'.(string) $index, 'string', $noProxy); + } + } + } + + private static function assertTlsFileOptionTypes( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (!isset($options[$option])) { + return; + } + + if (\is_string($options[$option])) { + return; + } + + if (!\is_array($options[$option])) { + self::invalidRequestOptionType($option, 'string|array{0: string, 1?: string}', $options[$option]); + + return; + } + + if (!\array_key_exists(0, $options[$option]) || !\is_string($options[$option][0])) { + self::invalidRequestOptionType($option.'.0', 'string', $options[$option][0] ?? null); + } + + if (\array_key_exists(1, $options[$option]) && $options[$option][1] !== null && !\is_string($options[$option][1])) { + self::invalidRequestOptionType($option.'.1', 'string|null', $options[$option][1]); + } + } + + private static function assertIfPresentAndNotArray( + #[\SensitiveParameter] + array $options, + string $option, + string $expected + ): void { + if (\array_key_exists($option, $options) && !\is_array($options[$option])) { + self::invalidRequestOptionType($option, $expected, $options[$option]); + } + } + + private static function assertIfPresentAndNotBool( + #[\SensitiveParameter] + array $options, + string $option, + ?string $path = null + ): void { + if (\array_key_exists($option, $options) && !\is_bool($options[$option])) { + self::invalidRequestOptionType($path ?? $option, 'bool', $options[$option]); + } + } + + private static function assertValidMultiplex( + #[\SensitiveParameter] + array $options + ): void { + if (!\array_key_exists('multiplex', $options) || $options['multiplex'] === null) { + return; + } + + if (!\in_array($options['multiplex'], [Multiplexing::NONE, Multiplexing::EAGER, Multiplexing::WAIT, Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new InvalidArgumentException(\sprintf( + 'The "multiplex" option must be null or a GuzzleHttp\\Multiplexing::* constant; received %s.', + \get_debug_type($options['multiplex']) + )); + } + } + + private static function assertIfPresentAndNotBoolOrInt( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_int($options[$option])) { + self::invalidRequestOptionType($option, 'bool|int', $options[$option]); + } + } + + private static function assertIfPresentAndNotBoolOrResource( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_resource($options[$option])) { + self::invalidRequestOptionType($option, 'bool|resource', $options[$option]); + } + } + + private static function assertIfPresentAndNotBoolOrString( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_string($options[$option])) { + self::invalidRequestOptionType($option, 'bool|string', $options[$option]); + } + } + + private static function assertIfPresentAndNotCallable( + #[\SensitiveParameter] + array $options, + string $option, + ?string $path = null + ): void { + if (\array_key_exists($option, $options) && !\is_callable($options[$option])) { + self::invalidRequestOptionType($path ?? $option, 'callable', $options[$option]); + } + } + + private static function assertIfPresentAndNotInt( + #[\SensitiveParameter] + array $options, + string $option, + ?string $path = null + ): void { + if (\array_key_exists($option, $options) && !\is_int($options[$option])) { + self::invalidRequestOptionType($path ?? $option, 'int', $options[$option]); + } + } + + private static function assertIfPresentAndNotNumber( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (\array_key_exists($option, $options) && !\is_int($options[$option]) && !\is_float($options[$option])) { + self::invalidRequestOptionType($option, 'int|float', $options[$option]); + } + } + + /** + * @param array $options + */ + private static function assertIfPresentAndNotFiniteNonNegativeNumber( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (!\array_key_exists($option, $options)) { + return; + } + + if (!\is_int($options[$option]) && !\is_float($options[$option])) { + self::invalidRequestOptionType($option, 'finite int|float greater than or equal to 0', $options[$option]); + + return; + } + + if (!\is_finite((float) $options[$option]) || $options[$option] < 0) { + self::invalidRequestOptionType($option, 'finite int|float greater than or equal to 0', $options[$option]); + } + } + + /** + * @param array $options + */ + private static function assertIfPresentAndNotForceIpResolve( + #[\SensitiveParameter] + array $options + ): void { + if (!\array_key_exists('force_ip_resolve', $options)) { + return; + } + + if ( + !\is_string($options['force_ip_resolve']) + || ($options['force_ip_resolve'] !== 'v4' && $options['force_ip_resolve'] !== 'v6') + ) { + self::invalidRequestOptionType('force_ip_resolve', '"v4"|"v6"', $options['force_ip_resolve']); + } + } + + private static function assertIfPresentAndNotString( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if (\array_key_exists($option, $options) && !\is_string($options[$option])) { + self::invalidRequestOptionType($option, 'string', $options[$option]); + } + } + + /** + * @param array $options + */ + private static function assertIfPresentAndNotProtocolArray( + #[\SensitiveParameter] + array $options, + string $option, + ?string $path = null + ): void { + if (!\array_key_exists($option, $options)) { + return; + } + + $path = $path ?? $option; + + if (!\is_array($options[$option]) || $options[$option] === []) { + self::invalidRequestOptionType($path, 'non-empty-array', $options[$option]); + + return; + } + + foreach ($options[$option] as $index => $protocol) { + if (!\is_string($protocol)) { + self::invalidRequestOptionType($path.'.'.(string) $index, 'string', $protocol); + + continue; + } + + if ($protocol !== 'http' && $protocol !== 'https') { + self::invalidRequestOptionType($path.'.'.(string) $index, '"http"|"https"', $protocol); + } + } + } + + private static function assertIfPresentAndNotStringOrNumber( + #[\SensitiveParameter] + array $options, + string $option + ): void { + if ( + \array_key_exists($option, $options) + && !\is_string($options[$option]) + && !\is_int($options[$option]) + && !\is_float($options[$option]) + ) { + self::invalidRequestOptionType($option, 'string|int|float', $options[$option]); + } + } + + /** + * @param mixed $value + */ + private static function invalidRequestOptionType( + string $option, + string $expected, + #[\SensitiveParameter] + $value + ): void { + throw new InvalidArgumentException(\sprintf( + 'Passing %s to request option "%s" is invalid; expected %s.', + \get_debug_type($value), + DiagnosticValue::escape($option), + $expected + )); + } + /** * Transfers the given request and applies request options. * * The URI of the request is not modified and the request options are used * as-is without merging in default options. * - * @param array $options See \GuzzleHttp\RequestOptions. + * @param array $options See {@see RequestOptions}. + * + * @return PromiseInterface */ - private function transfer(RequestInterface $request, array $options): PromiseInterface - { + private function transfer( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { $request = $this->applyOptions($request, $options); - /** @var HandlerStack $handler */ - $handler = $options['handler']; + + /** @var callable(RequestInterface, array): PromiseInterface $handler */ + $handler = $this->config['handler']; try { + self::assertRequestProtocolVersion($request); + + /** @var PromiseInterface */ return P\Create::promiseFor($handler($request, $options)); - } catch (\Exception $e) { + } catch (\Throwable $e) { + /** @var PromiseInterface */ return P\Create::rejectionFor($e); } } @@ -337,12 +1521,23 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface /** * Applies the array of request options to a request. */ - private function applyOptions(RequestInterface $request, array &$options): RequestInterface - { + private function applyOptions( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$options + ): RequestInterface { $modify = [ 'set_headers' => [], ]; + // Validate the response and stream factories up front. Every request + // yields a response, and the built-in handlers build the response body + // stream via the stream factory even when the request has no body, so + // both must be validated unconditionally. + self::requireResponseFactory($options[RequestOptions::RESPONSE_FACTORY] ?? new HttpFactory()); + $streamFactory = self::requireStreamFactory($options[RequestOptions::STREAM_FACTORY] ?? new HttpFactory()); + if (isset($options['headers'])) { if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) { throw new InvalidArgumentException('The headers array must have header name as keys.'); @@ -354,10 +1549,10 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface if (isset($options['form_params'])) { if (isset($options['multipart'])) { throw new InvalidArgumentException('You cannot use ' - . 'form_params and multipart at the same time. Use the ' - . 'form_params option if you want to send application/' - . 'x-www-form-urlencoded requests, and the multipart ' - . 'option to send multipart/form-data requests.'); + .'form_params and multipart at the same time. Use the ' + .'form_params option if you want to send application/' + .'x-www-form-urlencoded requests, and the multipart ' + .'option to send multipart/form-data requests.'); } $options['body'] = \http_build_query($options['form_params'], '', '&'); unset($options['form_params']); @@ -372,54 +1567,36 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface } if (isset($options['json'])) { - $options['body'] = Utils::jsonEncode($options['json']); + try { + $options['body'] = \json_encode($options['json'], \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new InvalidArgumentException('json_encode error: '.$e->getMessage(), 0, $e); + } + unset($options['json']); // Ensure that we don't have the header in different case and set the new value. $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); $options['_conditional']['Content-Type'] = 'application/json'; } - if (!empty($options['decode_content']) - && $options['decode_content'] !== true - ) { + if (isset($options['decode_content']) && \is_string($options['decode_content'])) { // Ensure that we don't have the header in different case and set the new value. $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); - $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + $modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content']; } if (isset($options['body'])) { if (\is_array($options['body'])) { throw $this->invalidBody(); } - $modify['body'] = Psr7\Utils::streamFor($options['body']); + $modify['body'] = self::createBodyStream($options['body'], $streamFactory); unset($options['body']); } - if (!empty($options['auth']) && \is_array($options['auth'])) { - $value = $options['auth']; - $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; - switch ($type) { - case 'basic': - // Ensure that we don't have the header in different case and set the new value. - $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); - $modify['set_headers']['Authorization'] = 'Basic ' - . \base64_encode("$value[0]:$value[1]"); - break; - case 'digest': - // @todo: Do not rely on curl - $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; - $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; - break; - case 'ntlm': - $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; - $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]"; - break; - } - } - if (isset($options['query'])) { $value = $options['query']; if (\is_array($value)) { + self::assertFiniteFloats($value, 'query'); $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986); } if (!\is_string($value)) { @@ -429,12 +1606,8 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface unset($options['query']); } - // Ensure that sink is not an invalid value. - if (isset($options['sink'])) { - // TODO: Add more sink validation? - if (\is_bool($options['sink'])) { - throw new InvalidArgumentException('sink must not be a boolean'); - } + if (isset($options['version'])) { + $modify['version'] = self::normalizeProtocolVersion($options['version']); } $request = Psr7\Utils::modifyRequest($request, $modify); @@ -442,8 +1615,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface // Use a multipart/form-data POST if a Content-Type is not set. // Ensure that we don't have the header in different case and set the new value. $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); - $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' - . $request->getBody()->getBoundary(); + $options['_conditional']['Content-Type'] = self::getMultipartContentType($request->getBody()); } // Merge in conditional headers if they are not present. @@ -451,8 +1623,9 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface // Build up the changes so it's in a single clone of the message. $modify = []; foreach ($options['_conditional'] as $k => $v) { - if (!$request->hasHeader($k)) { - $modify['set_headers'][$k] = $v; + $name = (string) $k; + if (!$request->hasHeader($name)) { + $modify['set_headers'][$name] = $v; } } $request = Psr7\Utils::modifyRequest($request, $modify); @@ -463,15 +1636,64 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface return $request; } + /** + * @param string|int|float $version + */ + private static function normalizeProtocolVersion($version): string + { + $version = \is_float($version) ? \number_format($version, 1, '.', '') : (string) $version; + + self::assertProtocolVersion($version); + + return $version; + } + + private static function assertProtocolVersion(string $version): void + { + if ('' === $version) { + throw new InvalidArgumentException('HTTP protocol version must not be empty.'); + } + + if (1 !== \preg_match('/^\d+(?:\.\d+)?$/D', $version)) { + throw new InvalidArgumentException('HTTP protocol version must be a valid HTTP version number.'); + } + } + + private static function assertRequestProtocolVersion( + #[\SensitiveParameter] + RequestInterface $request + ): void { + $version = $request->getProtocolVersion(); + + if ('' === $version) { + throw new RequestException('HTTP protocol version must not be empty.', $request); + } + + if (1 !== \preg_match('/^\d+(?:\.\d+)?$/D', $version)) { + throw new RequestException('HTTP protocol version must be a valid HTTP version number.', $request); + } + } + + private static function getMultipartContentType(Psr7\MultipartStream $body): string + { + $boundary = $body->getBoundary(); + + if (false !== \strpbrk($boundary, '()<>@,;:\"/[]?= ')) { + $boundary = '"'.$boundary.'"'; + } + + return 'multipart/form-data; boundary='.$boundary; + } + /** * Return an InvalidArgumentException with pre-set message. */ private function invalidBody(): InvalidArgumentException { return new InvalidArgumentException('Passing in the "body" request ' - . 'option as an array to send a request is not supported. ' - . 'Please use the "form_params" request option to send a ' - . 'application/x-www-form-urlencoded request, or the "multipart" ' - . 'request option to send a multipart/form-data request.'); + .'option as an array to send a request is not supported. ' + .'Please use the "form_params" request option to send a ' + .'application/x-www-form-urlencoded request, or the "multipart" ' + .'request option to send a multipart/form-data request.'); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php index 6aaee61af..187459b97 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -1,11 +1,19 @@ , + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply to the given request and to the transfer. * * @throws GuzzleException */ @@ -33,8 +113,82 @@ interface ClientInterface * Asynchronously send an HTTP request. * * @param RequestInterface $request Request to send - * @param array $options Request options to apply to the given - * request and to the transfer. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply to the given request and to the transfer. + * + * @return PromiseInterface */ public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface; @@ -45,9 +199,82 @@ interface ClientInterface * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string $method HTTP method. - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ @@ -58,27 +285,86 @@ interface ClientInterface * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string $method HTTP method - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; - - /** - * Get a client configuration option. - * - * These options include default request options of the client, a "handler" - * (if utilized by the concrete client), and a "base_uri" if utilized by - * the concrete client. - * - * @param string|null $option The config option to retrieve. - * - * @return mixed - * - * @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0. - */ - public function getConfig(?string $option = null); } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientTrait.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientTrait.php index c584c76cb..8eed76a9b 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientTrait.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ClientTrait.php @@ -1,10 +1,19 @@ , + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ @@ -34,13 +116,89 @@ trait ClientTrait * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ - public function get($uri, array $options = []): ResponseInterface - { + public function get( + $uri, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { return $this->request('GET', $uri, $options); } @@ -51,13 +209,89 @@ trait ClientTrait * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ - public function head($uri, array $options = []): ResponseInterface - { + public function head( + $uri, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { return $this->request('HEAD', $uri, $options); } @@ -68,13 +302,89 @@ trait ClientTrait * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ - public function put($uri, array $options = []): ResponseInterface - { + public function put( + $uri, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { return $this->request('PUT', $uri, $options); } @@ -85,13 +395,89 @@ trait ClientTrait * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ - public function post($uri, array $options = []): ResponseInterface - { + public function post( + $uri, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { return $this->request('POST', $uri, $options); } @@ -102,13 +488,89 @@ trait ClientTrait * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ - public function patch($uri, array $options = []): ResponseInterface - { + public function patch( + $uri, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { return $this->request('PATCH', $uri, $options); } @@ -119,13 +581,89 @@ trait ClientTrait * relative path to append to the base path of the client. The URL can * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. * * @throws GuzzleException */ - public function delete($uri, array $options = []): ResponseInterface - { + public function delete( + $uri, + #[\SensitiveParameter] + array $options = [] + ): ResponseInterface { return $this->request('DELETE', $uri, $options); } @@ -134,12 +672,86 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string $method HTTP method - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; @@ -148,14 +760,91 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ - public function getAsync($uri, array $options = []): PromiseInterface - { + public function getAsync( + $uri, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { return $this->requestAsync('GET', $uri, $options); } @@ -164,14 +853,91 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ - public function headAsync($uri, array $options = []): PromiseInterface - { + public function headAsync( + $uri, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { return $this->requestAsync('HEAD', $uri, $options); } @@ -180,14 +946,91 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ - public function putAsync($uri, array $options = []): PromiseInterface - { + public function putAsync( + $uri, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { return $this->requestAsync('PUT', $uri, $options); } @@ -196,14 +1039,91 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ - public function postAsync($uri, array $options = []): PromiseInterface - { + public function postAsync( + $uri, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { return $this->requestAsync('POST', $uri, $options); } @@ -212,14 +1132,91 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ - public function patchAsync($uri, array $options = []): PromiseInterface - { + public function patchAsync( + $uri, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { return $this->requestAsync('PATCH', $uri, $options); } @@ -228,14 +1225,91 @@ trait ClientTrait * * Use an absolute path to override the base path of the client, or a * relative path to append to the base path of the client. The URL can - * contain the query string as well. Use an array to provide a URL - * template and additional variables to use in the URL template expansion. + * contain the query string as well. * - * @param string|UriInterface $uri URI object or string. - * @param array $options Request options to apply. + * @param string|UriInterface $uri URI object or string. + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options Request options to apply. + * + * @return PromiseInterface */ - public function deleteAsync($uri, array $options = []): PromiseInterface - { + public function deleteAsync( + $uri, + #[\SensitiveParameter] + array $options = [] + ): PromiseInterface { return $this->requestAsync('DELETE', $uri, $options); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php index 9985a9814..e3ed1d1e3 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -1,7 +1,11 @@ strictMode = $strictMode; foreach ($cookieArray as $cookie) { - if (!($cookie instanceof SetCookie)) { + if (!$cookie instanceof SetCookie) { $cookie = new SetCookie($cookie); } $this->setCookie($cookie); @@ -45,15 +54,22 @@ class CookieJar implements CookieJarInterface * @param array $cookies Cookies to create the jar from * @param string $domain Domain to set the cookies to */ - public static function fromArray(array $cookies, string $domain): self - { + public static function fromArray( + #[\SensitiveParameter] + array $cookies, + string $domain + ): self { $cookieJar = new self(); foreach ($cookies as $name => $value) { + if (!\is_scalar($value) && !(\is_object($value) && \method_exists($value, '__toString'))) { + throw new \InvalidArgumentException('Cookie value must be scalar or stringable'); + } + $cookieJar->setCookie(new SetCookie([ - 'Domain' => $domain, - 'Name' => $name, - 'Value' => $value, - 'Discard' => true + 'Domain' => $domain, + 'Name' => (string) $name, + 'Value' => (string) $value, + 'Discard' => true, ])); } @@ -88,7 +104,7 @@ class CookieJar implements CookieJarInterface public function getCookieByName(string $name): ?SetCookie { foreach ($this->cookies as $cookie) { - if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + if ($cookie->getName() === $name) { return $cookie; } } @@ -96,9 +112,6 @@ class CookieJar implements CookieJarInterface return null; } - /** - * @inheritDoc - */ public function toArray(): array { return \array_map(static function (SetCookie $cookie): array { @@ -106,44 +119,41 @@ class CookieJar implements CookieJarInterface }, $this->getIterator()->getArrayCopy()); } - /** - * @inheritDoc - */ public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void { - if (!$domain) { + if ($domain === null) { $this->cookies = []; + return; - } elseif (!$path) { + } elseif ($path === null) { $this->cookies = \array_filter( $this->cookies, static function (SetCookie $cookie) use ($domain): bool { - return !$cookie->matchesDomain($domain); + return $cookie->getDomain() === null || !$cookie->matchesDomain($domain); } ); - } elseif (!$name) { + } elseif ($name === null) { $this->cookies = \array_filter( $this->cookies, static function (SetCookie $cookie) use ($path, $domain): bool { - return !($cookie->matchesPath($path) && - $cookie->matchesDomain($domain)); + return !($cookie->getDomain() !== null + && $cookie->matchesPath($path) + && $cookie->matchesDomain($domain)); } ); } else { $this->cookies = \array_filter( $this->cookies, - static function (SetCookie $cookie) use ($path, $domain, $name) { - return !($cookie->getName() == $name && - $cookie->matchesPath($path) && - $cookie->matchesDomain($domain)); + static function (SetCookie $cookie) use ($path, $domain, $name): bool { + return !($cookie->getDomain() !== null + && $cookie->getName() === $name + && $cookie->matchesPath($path) + && $cookie->matchesDomain($domain)); } ); } } - /** - * @inheritDoc - */ public function clearSessionCookies(): void { $this->cookies = \array_filter( @@ -154,11 +164,10 @@ class CookieJar implements CookieJarInterface ); } - /** - * @inheritDoc - */ - public function setCookie(SetCookie $cookie): bool - { + public function setCookie( + #[\SensitiveParameter] + SetCookie $cookie + ): bool { // If the name string is empty (but not 0), ignore the set-cookie // string entirely. $name = $cookie->getName(); @@ -170,9 +179,19 @@ class CookieJar implements CookieJarInterface $result = $cookie->validate(); if ($result !== true) { if ($this->strictMode) { - throw new \RuntimeException('Invalid cookie: ' . $result); + throw new \RuntimeException('Invalid cookie: '.$result); } $this->removeCookieIfEmpty($cookie); + + return false; + } + + $maxAge = $cookie->getMaxAge(); + if (($maxAge !== null && $maxAge <= 0) || $cookie->isExpired()) { + if ($cookie->getDomain() !== null) { + $this->removeCookie($cookie); + } + return false; } @@ -180,29 +199,21 @@ class CookieJar implements CookieJarInterface foreach ($this->cookies as $i => $c) { // Two cookies are identical, when their path, and domain are // identical. - if ($c->getPath() != $cookie->getPath() || - $c->getDomain() != $cookie->getDomain() || - $c->getName() != $cookie->getName() + if ($c->getPath() !== $cookie->getPath() + || $c->getDomain() !== $cookie->getDomain() + || $c->getHostOnly() !== $cookie->getHostOnly() + || $c->getName() !== $cookie->getName() ) { continue; } - // The previously set cookie is a discard cookie and this one is - // not so allow the new cookie to be set - if (!$cookie->getDiscard() && $c->getDiscard()) { - unset($this->cookies[$i]); - continue; - } - - // If the new cookie's expiration is further into the future, then - // replace the old cookie - if ($cookie->getExpires() > $c->getExpires()) { - unset($this->cookies[$i]); - continue; - } - - // If the value has changed, we better change it - if ($cookie->getValue() !== $c->getValue()) { + // Replace the old cookie when its value, expiry or flags changed + if ($cookie->getValue() !== $c->getValue() + || $cookie->getExpires() !== $c->getExpires() + || $cookie->getDiscard() !== $c->getDiscard() + || $cookie->getSecure() !== $c->getSecure() + || $cookie->getHttpOnly() !== $c->getHttpOnly() + ) { unset($this->cookies[$i]); continue; } @@ -229,31 +240,109 @@ class CookieJar implements CookieJarInterface return new \ArrayIterator(\array_values($this->cookies)); } - public function extractCookies(RequestInterface $request, ResponseInterface $response): void - { + public function extractCookies( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + ResponseInterface $response + ): void { if ($cookieHeader = $response->getHeader('Set-Cookie')) { + $uri = $request->getUri(); + $requestHost = HostIdentity::canonicalHost($uri->getHost()); + $secure = $uri->getScheme() === 'https'; + $accepted = 0; + foreach ($cookieHeader as $cookie) { + if (\strlen($cookie) > self::MAX_SET_COOKIE_FIELD_LENGTH) { + continue; + } + $sc = SetCookie::fromString($cookie); - if (!$sc->getDomain()) { - $sc->setDomain($request->getUri()->getHost()); + $domain = $sc->getDomain(); + if ($domain === null || $domain === '') { + $sc->setDomain($requestHost); + $sc->setHostOnly(true); + } else { + $sc->setHostOnly(false); } if (0 !== \strpos($sc->getPath(), '/')) { $sc->setPath($this->getCookiePathFromRequest($request)); } - if (!$sc->matchesDomain($request->getUri()->getHost())) { + if (!$sc->matchesDomain($requestHost)) { + continue; + } + if (!$secure && ($sc->getSecure() || $this->overlaysSecureCookie($sc))) { + continue; + } + $prefixName = Psr7\Utils::asciiToLower((string) $sc->getName()); + if (\str_starts_with($prefixName, '__secure-') && !$sc->getSecure()) { + continue; + } + if (\str_starts_with($prefixName, '__host-') && (!$sc->getSecure() || !$sc->getHostOnly() || $sc->getPath() !== '/' || !self::hasPathAttribute($cookie))) { continue; } // Note: At this point `$sc->getDomain()` being a public suffix should // be rejected, but we don't want to pull in the full PSL dependency. - $this->setCookie($sc); + if ($this->setCookie($sc) && ++$accepted === self::MAX_SET_COOKIE_FIELDS) { + break; + } } } } + private function overlaysSecureCookie(SetCookie $cookie): bool + { + foreach ($this->cookies as $stored) { + if (self::isSecureCookieOverlay($cookie, $stored)) { + return true; + } + } + + return false; + } + + private static function isSecureCookieOverlay(SetCookie $cookie, SetCookie $stored): bool + { + if ($stored->getName() !== $cookie->getName() || !$stored->getSecure() || $stored->isExpired()) { + return false; + } + + $domain = $cookie->getDomain(); + $storedDomain = $stored->getDomain(); + if ($domain === null || $storedDomain === null) { + return false; + } + + if (!HostIdentity::cookieDomainMatches($storedDomain, $domain) && !HostIdentity::cookieDomainMatches($domain, $storedDomain)) { + return false; + } + + return $stored->matchesPath($cookie->getPath()); + } + + /** + * Mirrors SetCookie::fromString()'s splitting because parsed cookies cannot + * distinguish an absent Path attribute from a defaulted path. + */ + private static function hasPathAttribute(string $header): bool + { + $parts = \explode(';', $header); + \array_shift($parts); + + foreach ($parts as $part) { + $separator = \strpos($part, '='); + if ($separator !== false && Psr7\Utils::caselessEquals(\trim(\substr($part, 0, $separator), " \t"), 'Path')) { + return true; + } + } + + return false; + } + /** * Computes cookie path following RFC 6265 section 5.1.4 * - * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + * @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4 */ private function getCookiePathFromRequest(RequestInterface $request): string { @@ -275,22 +364,37 @@ class CookieJar implements CookieJarInterface return \substr($uriPath, 0, $lastSlashPos); } - public function withCookieHeader(RequestInterface $request): RequestInterface - { + public function withCookieHeader( + #[\SensitiveParameter] + RequestInterface $request + ): RequestInterface { $values = []; + $headerLength = 8; $uri = $request->getUri(); $scheme = $uri->getScheme(); - $host = $uri->getHost(); + $host = HostIdentity::canonicalHost($uri->getHost()); $path = $uri->getPath() ?: '/'; foreach ($this->cookies as $cookie) { - if ($cookie->matchesPath($path) && - $cookie->matchesDomain($host) && - !$cookie->isExpired() && - (!$cookie->getSecure() || $scheme === 'https') + if ($cookie->getDomain() !== null + && $cookie->matchesPath($path) + && $cookie->matchesDomain($host) + && !$cookie->isExpired() + && (!$cookie->getSecure() || $scheme === 'https') ) { - $values[] = $cookie->getName() . '=' - . $cookie->getValue(); + $name = (string) $cookie->getName(); + $value = (string) $cookie->getValue(); + $separatorLength = $values === [] ? 0 : 2; + $valueLength = \strlen($name) + 1 + \strlen($value); + if ($headerLength + $separatorLength + $valueLength > self::MAX_COOKIE_HEADER_LENGTH) { + break; + } + + $values[] = $name.'='.$value; + $headerLength += $separatorLength + $valueLength; + if (\count($values) === self::MAX_REQUEST_COOKIES) { + break; + } } } @@ -306,12 +410,21 @@ class CookieJar implements CookieJarInterface private function removeCookieIfEmpty(SetCookie $cookie): void { $cookieValue = $cookie->getValue(); - if ($cookieValue === null || $cookieValue === '') { - $this->clear( - $cookie->getDomain(), - $cookie->getPath(), - $cookie->getName() - ); + if (($cookieValue === null || $cookieValue === '') && $cookie->getDomain() !== null) { + $this->removeCookie($cookie); } } + + private function removeCookie(SetCookie $cookie): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $stored) use ($cookie): bool { + return !($stored->getName() === $cookie->getName() + && $stored->getPath() === $cookie->getPath() + && $stored->getDomain() === $cookie->getDomain() + && $stored->getHostOnly() === $cookie->getHostOnly()); + } + ); + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php index 7df374b5b..b2efa3c11 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -1,5 +1,7 @@ */ interface CookieJarInterface extends \Countable, \IteratorAggregate diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php index 290236d54..1ba3bc5b3 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -1,23 +1,37 @@ load($cookieFile); } + + $this->autoSave = true; } /** @@ -44,7 +60,24 @@ class FileCookieJar extends CookieJar */ public function __destruct() { - $this->save($this->filename); + if ($this->autoSave) { + $this->save($this->filename); + } + } + + /** + * Disable automatic persistence after unserialization. + */ + public function __wakeup(): void + { + $this->autoSave = false; + } + + public function __unserialize(array $data): void + { + $this->autoSave = false; + + throw new \LogicException(static::class.' should never be unserialized'); } /** @@ -52,7 +85,8 @@ class FileCookieJar extends CookieJar * * @param string $filename File to save * - * @throws \RuntimeException if the file cannot be found or created + * @throws \RuntimeException if the cookie data cannot be encoded or the + * file cannot be written */ public function save(string $filename): void { @@ -60,42 +94,75 @@ class FileCookieJar extends CookieJar /** @var SetCookie $cookie */ foreach ($this as $cookie) { if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { - $json[] = $cookie->toArray(); + $data = $cookie->toArray(); + $data['HostOnly'] = $cookie->getHostOnly(); + $json[] = $data; } } - $jsonStr = Utils::jsonEncode($json); - if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { - throw new \RuntimeException("Unable to save file {$filename}"); + try { + $jsonStr = \json_encode($json, \JSON_HEX_TAG | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new \RuntimeException('Unable to encode cookie data', 0, $e); } + + if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException(\sprintf('Unable to save file %s', DiagnosticValue::escape($filename))); + } + + // Best-effort: restrict the cookie file to the owner so persisted + // cookies are not world-readable. + @\chmod($filename, 0600); } /** * Load cookies from a JSON formatted file. * * Old cookies are kept unless overwritten by newly loaded ones. + * Cookie records are constructed before any are passed to setCookie(). * * @param string $filename Cookie file to load. * - * @throws \RuntimeException if the file cannot be loaded. + * @throws \RuntimeException if the file cannot be loaded or is invalid */ public function load(string $filename): void { $json = \file_get_contents($filename); if (false === $json) { - throw new \RuntimeException("Unable to load file {$filename}"); + throw new \RuntimeException(\sprintf('Unable to load file %s', DiagnosticValue::escape($filename))); } if ($json === '') { return; } - $data = Utils::jsonDecode($json, true); - if (\is_array($data)) { - foreach ($data as $cookie) { - $this->setCookie(new SetCookie($cookie)); + $message = \sprintf('Invalid cookie file: %s', DiagnosticValue::escape($filename)); + + try { + $data = \json_decode($json, true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new \RuntimeException($message, 0, $e); + } + + // Associative decoding turns JSON objects into arrays, so inspect the root syntax too. + if (!\is_array($data) || \substr($json, \strspn($json, " \t\n\r"), 1) !== '[') { + throw new \RuntimeException($message); + } + + $cookies = []; + foreach ($data as $cookie) { + if (!\is_array($cookie) || !\array_key_exists('HostOnly', $cookie) || !\is_bool($cookie['HostOnly'])) { + throw new \RuntimeException($message); } - } elseif (\is_scalar($data) && !empty($data)) { - throw new \RuntimeException("Invalid cookie file: {$filename}"); + + try { + $cookies[] = new SetCookie($cookie); + } catch (\InvalidArgumentException $e) { + throw new \RuntimeException($message, 0, $e); + } + } + + foreach ($cookies as $cookie) { + $this->setCookie($cookie); } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php index 5d51ca982..9e703ec0e 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -1,21 +1,36 @@ sessionKey = $sessionKey; $this->storeSessionCookies = $storeSessionCookies; $this->load(); + $this->autoSave = true; } /** @@ -38,11 +56,30 @@ class SessionCookieJar extends CookieJar */ public function __destruct() { - $this->save(); + if ($this->autoSave) { + $this->save(); + } } /** - * Save cookies to the client session + * Disable automatic persistence after unserialization. + */ + public function __wakeup(): void + { + $this->autoSave = false; + } + + public function __unserialize(array $data): void + { + $this->autoSave = false; + + throw new \LogicException(static::class.' should never be unserialized'); + } + + /** + * Save cookies to the client session. + * + * @throws \RuntimeException if the cookie data cannot be encoded */ public function save(): void { @@ -50,28 +87,64 @@ class SessionCookieJar extends CookieJar /** @var SetCookie $cookie */ foreach ($this as $cookie) { if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { - $json[] = $cookie->toArray(); + $data = $cookie->toArray(); + $data['HostOnly'] = $cookie->getHostOnly(); + $json[] = $data; } } - $_SESSION[$this->sessionKey] = \json_encode($json); + try { + $json = \json_encode($json, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new \RuntimeException('Unable to encode cookie data', 0, $e); + } + + $_SESSION[$this->sessionKey] = $json; } /** - * Load the contents of the client session into the data array + * Load cookies from the client session. + * + * @throws \RuntimeException if the session contains invalid cookie data */ protected function load(): void { if (!isset($_SESSION[$this->sessionKey])) { return; } - $data = \json_decode($_SESSION[$this->sessionKey], true); - if (\is_array($data)) { - foreach ($data as $cookie) { - $this->setCookie(new SetCookie($cookie)); + + $message = 'Invalid cookie data'; + $json = $_SESSION[$this->sessionKey]; + if (!\is_string($json)) { + throw new \RuntimeException($message); + } + + try { + $data = \json_decode($json, true, 512, \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new \RuntimeException($message, 0, $e); + } + + // Associative decoding turns JSON objects into arrays, so inspect the root syntax too. + if (!\is_array($data) || \substr($json, \strspn($json, " \t\n\r"), 1) !== '[') { + throw new \RuntimeException($message); + } + + $cookies = []; + foreach ($data as $cookie) { + if (!\is_array($cookie) || !\array_key_exists('HostOnly', $cookie) || !\is_bool($cookie['HostOnly'])) { + throw new \RuntimeException($message); } - } elseif (\strlen($data)) { - throw new \RuntimeException("Invalid cookie data"); + + try { + $cookies[] = new SetCookie($cookie); + } catch (\InvalidArgumentException $e) { + throw new \RuntimeException($message, 0, $e); + } + } + + foreach ($cookies as $cookie) { + $this->setCookie($cookie); } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php index a613c77bf..6ce2213de 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -1,7 +1,12 @@ null, - 'Value' => null, - 'Domain' => null, - 'Path' => '/', - 'Max-Age' => null, - 'Expires' => null, - 'Secure' => false, - 'Discard' => false, - 'HttpOnly' => false + private const DEFAULTS = [ + 'Name' => null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false, ]; /** * @var array Cookie data */ - private $data; + private array $data; + + /** + * @var bool Whether this cookie was set without a Domain attribute. + */ + private bool $hostOnly = false; /** * Create a new SetCookie object from a string. * * @param string $cookie Set-Cookie header string */ - public static function fromString(string $cookie): self - { + public static function fromString( + #[\SensitiveParameter] + string $cookie + ): self { // Create the default return array - $data = self::$defaults; + $data = self::DEFAULTS; // Explode the cookie string using a series of semicolons - $pieces = \array_filter(\array_map('trim', \explode(';', $cookie))); + $pieces = \array_filter(\array_map(static function (string $piece): string { + return \trim($piece, " \t"); + }, \explode(';', $cookie))); // The name of the cookie (first kvp) must exist and include an equal sign. if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) { return new self($data); @@ -46,9 +60,9 @@ class SetCookie // Add the cookie pieces into the parsed data array foreach ($pieces as $part) { $cookieParts = \explode('=', $part, 2); - $key = \trim($cookieParts[0]); + $key = \trim($cookieParts[0], " \t"); $value = isset($cookieParts[1]) - ? \trim($cookieParts[1], " \n\r\t\0\x0B") + ? \trim($cookieParts[1], " \t") : true; // Only check for non-cookies when cookies have been found @@ -56,49 +70,115 @@ class SetCookie $data['Name'] = $key; $data['Value'] = $value; } else { - foreach (\array_keys(self::$defaults) as $search) { - if (!\strcasecmp($search, $key)) { - $data[$search] = $value; + foreach (\array_keys(self::DEFAULTS) as $search) { + if (Psr7\Utils::caselessEquals($search, $key)) { + if ($search === 'Max-Age') { + if (\is_string($value) && \preg_match('/^[+-]?[0-9]+$/D', $value) === 1) { + $maxAge = self::parseNumericInteger($value); + if ($maxAge !== null) { + $data[$search] = $maxAge; + } + } + } elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') { + if ($value) { + $data[$search] = true; + } + } elseif (\is_string($value)) { + $data[$search] = $value; + } continue 2; } } + if (Psr7\Utils::caselessEquals('HostOnly', $key)) { + continue; + } $data[$key] = $value; } } + // Max-Age takes precedence over Expires, RFC 6265 section 5.3 + if ($data['Max-Age'] !== null) { + $data['Expires'] = null; + } + return new self($data); } /** * @param array $data Array of cookie data provided by a Cookie parser */ - public function __construct(array $data = []) - { - /** @var array|null $replaced will be null in case of replace error */ - $replaced = \array_replace(self::$defaults, $data); - if ($replaced === null) { - throw new \InvalidArgumentException('Unable to replace the default values for the Cookie.'); + public function __construct( + #[\SensitiveParameter] + array $data = [] + ) { + $this->data = self::DEFAULTS; + self::validateFieldTypes($data); + + if (\array_key_exists('HostOnly', $data)) { + $this->setHostOnly($data['HostOnly']); + unset($data['HostOnly']); } - $this->data = $replaced; - // Extract the Expires value and turn it into a UNIX timestamp if needed - if (!$this->getExpires() && $this->getMaxAge()) { - // Calculate the Expires date - $this->setExpires(\time() + $this->getMaxAge()); - } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { - $this->setExpires($expires); + if (isset($data['Name'])) { + $this->setName($data['Name']); + } + + if (isset($data['Value'])) { + $this->setValue($data['Value']); + } + + if (isset($data['Domain'])) { + $this->setDomain($data['Domain']); + } + + if (isset($data['Path'])) { + $this->setPath($data['Path']); + } + + if (isset($data['Max-Age'])) { + $this->setMaxAge($data['Max-Age']); + } + + if (isset($data['Expires'])) { + $this->setExpires($data['Expires']); + } + + if (isset($data['Secure'])) { + $this->setSecure($data['Secure']); + } + + if (isset($data['Discard'])) { + $this->setDiscard($data['Discard']); + } + + if (isset($data['HttpOnly'])) { + $this->setHttpOnly($data['HttpOnly']); + } + + // Set the remaining values that don't have extra validation logic + foreach (array_diff(array_keys($data), array_keys(self::DEFAULTS)) as $key) { + $this->data[$key] = $data[$key]; + } + + // Derive Expires from Max-Age unless the data already carries one + $maxAge = $this->getMaxAge(); + if ($maxAge !== null && $this->getExpires() === null) { + $this->setExpires(self::maxAgeToExpires($maxAge, \time())); } } - public function __toString() + public function __toString(): string { - $str = $this->data['Name'] . '=' . ($this->data['Value'] ?? '') . '; '; + $str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; '; foreach ($this->data as $k => $v) { + if ($k === 'Domain' && $this->getHostOnly()) { + continue; + } if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { if ($k === 'Expires') { - $str .= 'Expires=' . \gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + $str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; '; } else { - $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + $str .= ($v === true ? $k : "{$k}={$v}").'; '; } } } @@ -108,15 +188,18 @@ class SetCookie public function toArray(): array { - return $this->data; + $data = $this->data; + if ($this->getHostOnly()) { + $data['HostOnly'] = true; + } + + return $data; } /** * Get the cookie name. - * - * @return string */ - public function getName() + public function getName(): ?string { return $this->data['Name']; } @@ -126,21 +209,15 @@ class SetCookie * * @param string $name Cookie name */ - public function setName($name): void + public function setName(string $name): void { - if (!is_string($name)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['Name'] = (string) $name; + $this->data['Name'] = $name; } /** * Get the cookie value. - * - * @return string|null */ - public function getValue() + public function getValue(): ?string { return $this->data['Value']; } @@ -150,21 +227,15 @@ class SetCookie * * @param string $value Cookie value */ - public function setValue($value): void + public function setValue(string $value): void { - if (!is_string($value)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['Value'] = (string) $value; + $this->data['Value'] = $value; } /** * Get the domain. - * - * @return string|null */ - public function getDomain() + public function getDomain(): ?string { return $this->data['Domain']; } @@ -172,23 +243,35 @@ class SetCookie /** * Set the domain of the cookie. * - * @param string|null $domain + * @param string|null $domain Domain of the cookie */ - public function setDomain($domain): void + public function setDomain(?string $domain): void { - if (!is_string($domain) && null !== $domain) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } + $this->data['Domain'] = null === $domain ? null : self::normalizeDomain($domain); + } - $this->data['Domain'] = null === $domain ? null : (string) $domain; + /** + * Get whether this cookie is scoped to the origin host only. + */ + public function getHostOnly(): bool + { + return $this->hostOnly; + } + + /** + * Set whether this cookie is scoped to the origin host only. + * + * @param bool $hostOnly Set to true for host-only cookies + */ + public function setHostOnly(bool $hostOnly): void + { + $this->hostOnly = $hostOnly; } /** * Get the path. - * - * @return string */ - public function getPath() + public function getPath(): string { return $this->data['Path']; } @@ -198,21 +281,15 @@ class SetCookie * * @param string $path Path of the cookie */ - public function setPath($path): void + public function setPath(string $path): void { - if (!is_string($path)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['Path'] = (string) $path; + $this->data['Path'] = $path; } /** * Maximum lifetime of the cookie in seconds. - * - * @return int|null */ - public function getMaxAge() + public function getMaxAge(): ?int { return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; } @@ -222,21 +299,15 @@ class SetCookie * * @param int|null $maxAge Max age of the cookie in seconds */ - public function setMaxAge($maxAge): void + public function setMaxAge(?int $maxAge): void { - if (!is_int($maxAge) && null !== $maxAge) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + $this->data['Max-Age'] = $maxAge; } /** * The UNIX timestamp when the cookie Expires. - * - * @return string|int|null */ - public function getExpires() + public function getExpires(): ?int { return $this->data['Expires']; } @@ -249,18 +320,36 @@ class SetCookie public function setExpires($timestamp): void { if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + // TODO: Move this to the parameter definition in 9.0. + throw new \TypeError(__METHOD__.'(): Argument #1 ($timestamp) must be of type int|string|null'); } - $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + if ($timestamp === null) { + $this->data['Expires'] = null; + + return; + } + + if (\is_string($timestamp)) { + if (\is_numeric($timestamp)) { + $this->data['Expires'] = self::parseNumericInteger($timestamp); + + return; + } + } elseif (\is_int($timestamp)) { + $this->data['Expires'] = $timestamp; + + return; + } + + $expires = \strtotime($timestamp); + $this->data['Expires'] = $expires === false ? null : $expires; } /** * Get whether or not this is a secure cookie. - * - * @return bool */ - public function getSecure() + public function getSecure(): bool { return $this->data['Secure']; } @@ -270,21 +359,15 @@ class SetCookie * * @param bool $secure Set to true or false if secure */ - public function setSecure($secure): void + public function setSecure(bool $secure): void { - if (!is_bool($secure)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['Secure'] = (bool) $secure; + $this->data['Secure'] = $secure; } /** * Get whether or not this is a session cookie. - * - * @return bool|null */ - public function getDiscard() + public function getDiscard(): bool { return $this->data['Discard']; } @@ -294,21 +377,15 @@ class SetCookie * * @param bool $discard Set to true or false if this is a session cookie */ - public function setDiscard($discard): void + public function setDiscard(bool $discard): void { - if (!is_bool($discard)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['Discard'] = (bool) $discard; + $this->data['Discard'] = $discard; } /** * Get whether or not this is an HTTP only cookie. - * - * @return bool */ - public function getHttpOnly() + public function getHttpOnly(): bool { return $this->data['HttpOnly']; } @@ -318,13 +395,9 @@ class SetCookie * * @param bool $httpOnly Set to true or false if this is HTTP only */ - public function setHttpOnly($httpOnly): void + public function setHttpOnly(bool $httpOnly): void { - if (!is_bool($httpOnly)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - $this->data['HttpOnly'] = (bool) $httpOnly; + $this->data['HttpOnly'] = $httpOnly; } /** @@ -347,7 +420,7 @@ class SetCookie $cookiePath = $this->getPath(); // Match on exact matches or when path is the default empty "/" - if ($cookiePath === '/' || $cookiePath == $requestPath) { + if ($cookiePath === '/' || $cookiePath === $requestPath) { return true; } @@ -373,28 +446,15 @@ class SetCookie public function matchesDomain(string $domain): bool { $cookieDomain = $this->getDomain(); - if (null === $cookieDomain) { - return true; - } - - // Remove the leading '.' as per spec in RFC 6265. - // https://tools.ietf.org/html/rfc6265#section-5.2.3 - $cookieDomain = \ltrim(\strtolower($cookieDomain), '.'); - - $domain = \strtolower($domain); - - // Domain not set or exact match. - if ('' === $cookieDomain || $domain === $cookieDomain) { - return true; - } - - // Matching the subdomain according to RFC 6265. - // https://tools.ietf.org/html/rfc6265#section-5.1.3 - if (\filter_var($domain, \FILTER_VALIDATE_IP)) { + if (null === $cookieDomain || $cookieDomain === '') { return false; } - return (bool) \preg_match('/\.' . \preg_quote($cookieDomain, '/') . '$/', $domain); + if ($this->getHostOnly()) { + return HostIdentity::canonicalCookieDomain($domain) === HostIdentity::canonicalCookieDomain($cookieDomain); + } + + return HostIdentity::cookieDomainMatches($domain, $cookieDomain); } /** @@ -408,23 +468,20 @@ class SetCookie /** * Check if the cookie is valid according to RFC 6265. * - * @return bool|string Returns true if valid or an error message if invalid + * @return string|true Returns true if valid or an error message if invalid */ public function validate() { $name = $this->getName(); - if ($name === '') { + if ($name === null || $name === '') { return 'The cookie name must not be empty'; } // Check if any of the invalid characters are present in the cookie name - if (\preg_match( - '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', - $name - )) { + if (\preg_match('/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/', $name) !== 0) { return 'Cookie name must not contain invalid characters: ASCII ' - . 'Control characters (0-31;127), space, tab and the ' - . 'following characters: ()<>@,;:\"/?={}'; + .'Control characters (0-31;127), space, tab and the ' + .'following characters: ()<>@,;:\"/?={}'; } // Value must not be null. 0 and empty string are valid. Empty strings @@ -434,13 +491,111 @@ class SetCookie return 'The cookie value must not be empty'; } - // Domains must not be empty, but can be 0. "0" is not a valid internet - // domain, but may be used as server name in a private network. + // Domains must not be empty, but may be omitted. "0" is not a valid + // internet domain, but may be used as server name in a private network. $domain = $this->getDomain(); - if ($domain === null || $domain === '') { + if ($domain === '' || ($domain !== null && \ltrim(\trim($domain, " \n\r\t\0\x0B"), '.') === '')) { return 'The cookie domain must not be empty'; } + if ($this->getHostOnly() && $domain === null) { + return 'Host-only cookies must have a domain'; + } + return true; } + + private static function normalizeDomain(string $domain): string + { + $domain = Psr7\Utils::asciiToLower($domain); + + // Treat trailing-dot domains as host-only, but keep pure-dot domains invalid. + if ($domain !== '' && \substr($domain, -1) === '.' && \trim($domain, '.') !== '') { + return ''; + } + + if ($domain !== '' && $domain !== '.' && $domain[0] === '.') { + $domain = \substr_replace($domain, '', 0, 1); + } + + return HostIdentity::canonicalCookieDomain($domain); + } + + private static function maxAgeToExpires(int $maxAge, int $now): int + { + if ($maxAge <= 0) { + return $now - 1; + } + + // Clamp absurd Max-Age values so addition cannot promote to float + if ($maxAge > \PHP_INT_MAX - $now) { + return \PHP_INT_MAX; + } + + return $now + $maxAge; + } + + private static function parseNumericInteger(string $value): ?int + { + if (!\is_numeric($value)) { + return null; + } + + if (\preg_match('/^[+-]?[0-9]+$/D', $value) === 1) { + $negative = $value[0] === '-'; + $digits = \ltrim($value, '+-'); + $digits = \ltrim($digits, '0'); + $digits = $digits === '' ? '0' : $digits; + $limit = $negative ? \substr((string) \PHP_INT_MIN, 1) : (string) \PHP_INT_MAX; + + if (\strlen($digits) > \strlen($limit) || (\strlen($digits) === \strlen($limit) && \strcmp($digits, $limit) > 0)) { + return null; + } + + return (int) ($negative ? '-'.$digits : $digits); + } + + $number = (float) $value; + if (!\is_finite($number) || $number < \PHP_INT_MIN || $number > \PHP_INT_MAX) { + return null; + } + + if (\PHP_INT_SIZE === 8 && ($number <= (float) \PHP_INT_MIN || $number >= (float) \PHP_INT_MAX)) { + return null; + } + + return (int) $number; + } + + /** + * @param mixed[] $data + */ + private static function validateFieldTypes( + #[\SensitiveParameter] + array $data + ): void { + foreach (['Name', 'Value', 'Domain', 'Path'] as $field) { + if (isset($data[$field]) && !\is_string($data[$field])) { + throw new \InvalidArgumentException(\sprintf('Cookie field "%s" must be a string', $field)); + } + } + + if (isset($data['Max-Age']) && !\is_int($data['Max-Age'])) { + throw new \InvalidArgumentException('Cookie field "Max-Age" must be an integer'); + } + + if (isset($data['Expires']) && !\is_int($data['Expires']) && !\is_string($data['Expires'])) { + throw new \InvalidArgumentException('Cookie field "Expires" must be an integer or string'); + } + + foreach (['Secure', 'Discard', 'HttpOnly'] as $field) { + if (isset($data[$field]) && !\is_bool($data[$field])) { + throw new \InvalidArgumentException(\sprintf('Cookie field "%s" must be a boolean', $field)); + } + } + + if (\array_key_exists('HostOnly', $data) && !\is_bool($data['HostOnly'])) { + throw new \InvalidArgumentException('Cookie field "HostOnly" must be a boolean'); + } + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Env.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Env.php new file mode 100644 index 000000000..aaabd9a4d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Env.php @@ -0,0 +1,33 @@ +request = $request; - $this->handlerContext = $handlerContext; - } - - /** - * Get the request that caused the exception - */ - public function getRequest(): RequestInterface - { - return $this->request; - } - - /** - * Get contextual information about the error from the underlying handler. - * - * The contents of this array will vary depending on which handler you are - * using. It may also be just an empty array. Relying on this data will - * couple you to a specific handler, but can give more debug information - * when needed. - */ - public function getHandlerContext(): array - { - return $this->handlerContext; - } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ConnectTimeoutException.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ConnectTimeoutException.php new file mode 100644 index 000000000..61af5f200 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ConnectTimeoutException.php @@ -0,0 +1,12 @@ +getStatusCode() : 0; - parent::__construct($message, $code, $previous); - $this->request = $request; - $this->response = $response; - $this->handlerContext = $handlerContext; - } - - /** - * Wrap non-RequestExceptions with a RequestException - */ - public static function wrapException(RequestInterface $request, \Throwable $e): RequestException - { - return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + parent::__construct($message, $request, $code, $previous); } /** * Factory method to create a new exception with a normalized error message * * @param RequestInterface $request Request sent - * @param ResponseInterface $response Response received + * @param ResponseInterface|null $response Response received, if any * @param \Throwable|null $previous Previous exception - * @param array $handlerContext Optional handler context * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer */ public static function create( + #[\SensitiveParameter] RequestInterface $request, - ResponseInterface $response = null, - \Throwable $previous = null, - array $handlerContext = [], - BodySummarizerInterface $bodySummarizer = null + #[\SensitiveParameter] + ?ResponseInterface $response = null, + #[\SensitiveParameter] + ?\Throwable $previous = null, + ?BodySummarizerInterface $bodySummarizer = null ): self { if (!$response) { return new self( 'Error completing request', $request, - null, - $previous, - $handlerContext + 0, + $previous ); } $level = (int) \floor($response->getStatusCode() / 100); if ($level === 4) { $label = 'Client error'; - $className = ClientException::class; } elseif ($level === 5) { $label = 'Server error'; - $className = ServerException::class; } else { $label = 'Unsuccessful request'; - $className = __CLASS__; } - $uri = $request->getUri(); - $uri = static::obfuscateUri($uri); + $uri = \GuzzleHttp\Psr7\Utils::redactUriForMessage($request->getUri()); - // Client Error: `GET /` resulted in a `404 Not Found` response: - // ... (truncated) + // Client error: `GET /` resulted in a `404 Not Found` response: ... (truncated) $message = \sprintf( '%s: `%s %s` resulted in a `%s %s` response', $label, - $request->getMethod(), - $uri->__toString(), + DiagnosticValue::escape($request->getMethod()), + $uri, $response->getStatusCode(), - $response->getReasonPhrase() + DiagnosticValue::escape($response->getReasonPhrase()) ); $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); if ($summary !== null) { - $message .= ":\n{$summary}\n"; + $message .= \sprintf(': %s', DiagnosticValue::escape($summary)); } - return new $className($message, $request, $response, $previous, $handlerContext); - } - - /** - * Obfuscates URI if there is a username and a password present - */ - private static function obfuscateUri(UriInterface $uri): UriInterface - { - $userInfo = $uri->getUserInfo(); - - if (false !== ($pos = \strpos($userInfo, ':'))) { - return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + if ($level === 4) { + return new ClientException($message, $request, $response, $previous); } - return $uri; - } + if ($level === 5) { + return new ServerException($message, $request, $response, $previous); + } - /** - * Get the request that caused the exception - */ - public function getRequest(): RequestInterface - { - return $this->request; - } - - /** - * Get the associated response - */ - public function getResponse(): ?ResponseInterface - { - return $this->response; - } - - /** - * Check if a response was received - */ - public function hasResponse(): bool - { - return $this->response !== null; - } - - /** - * Get contextual information about the error from the underlying handler. - * - * The contents of this array will vary depending on which handler you are - * using. It may also be just an empty array. Relying on this data will - * couple you to a specific handler, but can give more debug information - * when needed. - */ - public function getHandlerContext(): array - { - return $this->handlerContext; + return new ResponseException($message, $request, $response, $previous); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ResponseException.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ResponseException.php new file mode 100644 index 000000000..b5cd96494 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ResponseException.php @@ -0,0 +1,63 @@ +getStatusCode(), $previous); + $this->response = $response; + } + + /** + * Return a new exception instance with an updated response. + * + * @param \Throwable|null $previous Previous exception, or the current + * previous exception when omitted. + * + * @return static + */ + public function withResponse( + #[\SensitiveParameter] + ResponseInterface $response, + #[\SensitiveParameter] + ?\Throwable $previous = null + ): self { + if ($response->getStatusCode() !== $this->response->getStatusCode()) { + throw new InvalidArgumentException('Cannot replace response with a different status code.'); + } + + return new static( + $this->getMessage(), + $this->getRequest(), + $response, + $previous ?? $this->getPrevious() + ); + } + + /** + * Get the associated response. + */ + public function getResponse(): ResponseInterface + { + return $this->response; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ResponseTimeoutException.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ResponseTimeoutException.php new file mode 100644 index 000000000..49694415e --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Exception/ResponseTimeoutException.php @@ -0,0 +1,13 @@ +request = $request; + } + + /** + * Get the request that caused the exception. + */ + public function getRequest(): RequestInterface + { + return $this->request; + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/ChunkedBodyStream.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/ChunkedBodyStream.php new file mode 100644 index 000000000..e8e4b8778 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/ChunkedBodyStream.php @@ -0,0 +1,431 @@ +stream = $stream; + } + + public function __clone() + { + // A clone shares the transport, so detaching it would break this body. + $this->detachable = false; + } + + public function read(int $length): string + { + if ($this->failure !== null) { + throw $this->failure; + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + do { + $data = $this->decode($length); + } while ($data === '' && $length > 0 && !$this->eof() && $this->fill()); + + $this->position = TransferByteCounter::add( + $this->position, + \strlen($data), + 'Response body exceeds the maximum integer size supported on this platform' + ); + + return $data; + } + + public function eof(): bool + { + return $this->state === self::DONE; + } + + /** + * @return mixed + */ + public function getMetadata(?string $key = null) + { + // A failed body has not timed out, whatever a stale flag on the + // transport says, so callers report the failure itself. + if ($this->failure !== null && $key === 'timed_out') { + return false; + } + + $metadata = $this->stream->getMetadata($key); + if ($this->failure !== null && $key === null && \is_array($metadata)) { + $metadata['timed_out'] = false; + } + + return $metadata; + } + + public function tell(): int + { + return $this->position; + } + + public function getSize(): ?int + { + return null; + } + + public function isSeekable(): bool + { + return false; + } + + public function seek(int $offset, int $whence = \SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a chunked response body'); + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + throw new \RuntimeException('Cannot write to a chunked response body'); + } + + public function close(): void + { + $this->buffer = ''; + $this->offset = 0; + $this->detachable = false; + $this->stream->close(); + } + + public function detach() + { + if (!$this->detachable) { + return null; + } + + if ($this->state === self::DONE) { + return $this->stream->detach(); + } + + if ($this->buffer === '' && $this->failure === null) { + $resource = $this->stream->detach(); + if (\is_resource($resource)) { + // Nothing has been decoded yet, so PHP's filter can take over. + \stream_filter_append($resource, 'dechunk', \STREAM_FILTER_READ); + } + + return $resource; + } + + // PHP's filter cannot resume part way through a chunk, so hand the + // decoder over to a resource that keeps decoding the rest of the body. + $decoder = clone $this; + $this->buffer = ''; + $this->offset = 0; + $this->detachable = false; + $this->stream = Utils::streamFor(''); + $this->stream->detach(); + + return StreamWrapper::getResource($decoder); + } + + /** + * Appends what the transport has available to the buffer, waiting for at + * least one byte. + */ + private function fill(): bool + { + $data = $this->stream->read(self::READ_SIZE); + if ($data === '') { + if (!$this->stream->eof()) { + return false; + } + + $this->failure = new \RuntimeException('Response body ended before the chunked body was complete'); + + throw $this->failure; + } + + $this->buffer = \substr($this->buffer, $this->offset).$data; + $this->offset = 0; + + return true; + } + + /** + * Decodes up to $length bytes from the buffer, and any framing after them, + * so the end of the body is noticed without another transport read. + */ + private function decode(int $length): string + { + $data = ''; + + try { + while ($this->offset < \strlen($this->buffer) && $this->state !== self::DONE) { + if ($this->state === self::DATA) { + $take = \min($this->remaining, $length - \strlen($data), \strlen($this->buffer) - $this->offset); + if ($take === 0) { + break; + } + $data .= \substr($this->buffer, $this->offset, $take); + $this->offset += $take; + $this->remaining -= $take; + if ($this->remaining === 0) { + $this->state = self::DATA_END; + } + } elseif ($this->state === self::SIZE) { + $chunks = $this->digits === '' ? $this->decodeChunks($length - \strlen($data)) : null; + if ($chunks === null) { + $this->decodeSize(); + } else { + $data .= $chunks; + } + } elseif ($this->state === self::SIZE_LINE) { + $this->skipSizeLine(); + } elseif ($this->state === self::DATA_END) { + $this->decodeDataEnd(); + } else { + $this->decodeTrailer(); + } + } + } catch (\RuntimeException $e) { + $this->failure = $e; + if ($data === '') { + throw $e; + } + } + + return $data; + } + + /** + * Decodes buffered chunks in bulk, up to $length bytes, and leaves the + * state machine at the first chunk that needs more care. Returns null when + * it consumes nothing. + */ + private function decodeChunks(int $length): ?string + { + $buffer = $this->buffer; + $end = \strlen($buffer); + $offset = $this->offset; + $data = ''; + + while (true) { + // Up to seven digits keep the size an int on every platform. + $digits = \strspn($buffer, '0123456789abcdefABCDEF', $offset, 8); + if ($digits === 0 || $digits === 8) { + break; + } + $lineEnd = \strpos($buffer, "\n", $offset + $digits); + if ($lineEnd === false) { + break; + } + + $size = (int) \hexdec(\substr($buffer, $offset, $digits)); + $offset = $lineEnd + 1; + if ($size === 0) { + $this->state = self::TRAILER; + break; + } + if ($size > $length || $offset + $size > $end) { + $this->remaining = $size; + $this->state = self::DATA; + break; + } + + $data .= \substr($buffer, $offset, $size); + $length -= $size; + $offset += $size; + if ($offset + 1 < $end && $buffer[$offset] === "\r" && $buffer[$offset + 1] === "\n") { + $offset += 2; + } elseif ($offset < $end && $buffer[$offset] === "\n") { + ++$offset; + } else { + $this->state = self::DATA_END; + break; + } + } + + if ($offset === $this->offset) { + return null; + } + + $this->offset = $offset; + + return $data; + } + + private function decodeSize(): void + { + $count = \strspn($this->buffer, '0123456789abcdefABCDEF', $this->offset); + $this->digits .= \substr($this->buffer, $this->offset, $count); + $this->offset += $count; + if (\strlen($this->digits) > self::MAX_SIZE_DIGITS) { + throw new \RuntimeException('Invalid chunked response body: invalid chunk size'); + } + if ($this->offset === \strlen($this->buffer)) { + return; + } + if ($this->digits === '') { + throw new \RuntimeException('Invalid chunked response body: invalid chunk size'); + } + + $significant = \ltrim($this->digits, '0'); + if (\strlen($significant) > 16 || (\strlen($significant) === 16 && $significant[0] > '7')) { + throw new \RuntimeException('Invalid chunked response body: invalid chunk size'); + } + + $size = \hexdec($this->digits); + if (!\is_int($size)) { + throw new \OverflowException('Chunk size exceeds the maximum integer size supported on this platform'); + } + + $this->digits = ''; + $this->remaining = $size; + $this->state = self::SIZE_LINE; + } + + private function skipSizeLine(): void + { + // Chunk extensions, and anything else up to the line feed, are ignored + // the way libcurl ignores them. + $end = \strpos($this->buffer, "\n", $this->offset); + if ($end === false) { + $this->offset = \strlen($this->buffer); + + return; + } + + $this->offset = $end + 1; + $this->state = $this->remaining === 0 ? self::TRAILER : self::DATA; + } + + private function decodeDataEnd(): void + { + $this->offset += \strspn($this->buffer, "\r", $this->offset); + if ($this->offset === \strlen($this->buffer)) { + return; + } + if ($this->buffer[$this->offset] !== "\n") { + throw new \RuntimeException('Invalid chunked response body: missing CRLF after chunk data'); + } + + ++$this->offset; + $this->state = self::SIZE; + } + + private function decodeTrailer(): void + { + if ($this->state !== self::TRAILER) { + if ($this->buffer[$this->offset] !== "\n") { + throw new \RuntimeException('Invalid chunked response body: invalid trailer section'); + } + + ++$this->offset; + $this->state = $this->state === self::FINAL_CR ? self::DONE : self::TRAILER; + + return; + } + + $count = \strcspn($this->buffer, "\r\n", $this->offset); + $this->line .= \substr($this->buffer, $this->offset, $count); + $this->offset += $count; + $this->trailerLength += $count; + if (\strlen($this->line) > self::MAX_TRAILER_LINE_LENGTH || $this->trailerLength > self::MAX_TRAILER_SECTION_LENGTH) { + throw new \RuntimeException('Invalid chunked response body: invalid trailer section'); + } + if ($this->offset === \strlen($this->buffer)) { + return; + } + + $carriageReturn = $this->buffer[$this->offset] === "\r"; + ++$this->offset; + if ($this->line === '') { + $this->state = $carriageReturn ? self::FINAL_CR : self::DONE; + + return; + } + + if (\strpos($this->line, "\0") !== false || \strpos($this->line, ':') === false) { + throw new \RuntimeException('Invalid chunked response body: invalid trailer section'); + } + + $this->line = ''; + if ($carriageReturn) { + $this->state = self::TRAILER_CR; + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Clock.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Clock.php new file mode 100644 index 000000000..20f284bfe --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Clock.php @@ -0,0 +1,32 @@ + 0) { + $slice = \min($microseconds, 0xFFFFFFFF); + \usleep($slice); + $microseconds -= $slice; + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php index 0c45089f1..aa3e6db42 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php @@ -1,61 +1,317 @@ true, // CURLE_COULDNT_RESOLVE_PROXY + 6 => true, // CURLE_COULDNT_RESOLVE_HOST + 7 => true, // CURLE_COULDNT_CONNECT + 35 => true, // CURLE_SSL_CONNECT_ERROR + 51 => true, // CURLE_PEER_FAILED_VERIFICATION before libcurl 7.62.0 + 60 => true, // CURLE_SSL_CACERT / modern CURLE_PEER_FAILED_VERIFICATION + 83 => true, // CURLE_SSL_ISSUER_ERROR + 90 => true, // CURLE_SSL_PINNEDPUBKEYNOTMATCH + 91 => true, // CURLE_SSL_INVALIDCERTSTATUS + 96 => true, // CURLE_QUIC_CONNECT_ERROR + 97 => true, // CURLE_PROXY + 98 => true, // CURLE_SSL_CLIENTCERT + 101 => true, // CURLE_ECH_REQUIRED + ]; + + private const CURL_NETWORK_ERRORS = [ + 16 => true, // CURLE_HTTP2 + 52 => true, // CURLE_GOT_NOTHING + 55 => true, // CURLE_SEND_ERROR + 56 => true, // CURLE_RECV_ERROR + 92 => true, // CURLE_HTTP2_STREAM + 95 => true, // CURLE_HTTP3 + ]; + + private const CURL_RESPONSE_TRANSFER_ERRORS = [ + 8 => true, // CURLE_WEIRD_SERVER_REPLY + 18 => true, // CURLE_PARTIAL_FILE + 23 => true, // CURLE_WRITE_ERROR from libcurl's content decoders + 61 => true, // CURLE_BAD_CONTENT_ENCODING + ]; + + private const CURL_CONNECT_TIMEOUT_ERRORS = [ + 'Connection timed out', + 'Connection timeout', + 'Connection time-out', + 'Resolving timed out', + 'name lookup timed out', + 'Proxy CONNECT aborted due to timeout', + 'SSL connection timeout', + ]; + + /** + * libcurl's CURL_READFUNC_ABORT value. + */ + private const CURL_READFUNC_ABORT = 0x10000000; + + /** + * libcurl's CURL_SEEKFUNC_OK value. + */ + private const CURL_SEEKFUNC_OK = 0; + + /** + * libcurl's CURL_SEEKFUNC_FAIL value. + */ + private const CURL_SEEKFUNC_FAIL = 1; + + /** + * libcurl's CURL_SEEKFUNC_CANTSEEK value. + */ + private const CURL_SEEKFUNC_CANTSEEK = 2; + + /** + * Maximum number of times libcurl may replay a streamed request body. + */ + private const CURL_SEEK_MAX_REPLAYS = 3; + + /** + * libcurl's CURLE_SEND_FAIL_REWIND value. + */ + private const CURLE_SEND_FAIL_REWIND = 65; + + /** + * Guzzle's default connect timeout in milliseconds, replacing libcurl's + * 300 seconds. + */ + private const DEFAULT_CONNECT_TIMEOUT_MS = 60000; + + /** + * Milliseconds passed to libcurl to disable the connect timeout: libcurl + * treats zero as its own 300-second default, so disabling means passing + * the largest value a 32-bit libcurl long accepts. + */ + private const CONNECT_TIMEOUT_DISABLED_MS = 2147483647; /** * @var resource[]|\CurlHandle[] */ - private $handles = []; + private array $handles = []; + + /** + * @var string|null Owner signature of the proxy tunnels that pooled idle + * handles may still hold + */ + private ?string $proxyTunnelOwner = null; + + /** + * @var bool Whether an in-domain handle has been pooled since the last purge + */ + private bool $poolMayHoldTunnels = false; /** * @var int Total number of idle handles to keep in cache */ - private $maxHandles; + private int $maxHandles; + + private bool $closed = false; /** - * @param int $maxHandles Maximum number of idle handles. + * @var resource|\CurlShareHandle|\CurlSharePersistentHandle|null */ - public function __construct(int $maxHandles) + private $shareHandle; + + private string $shareMode; + + /** + * @var bool Whether the configured share handle may own a connection + * cache populated outside this factory + */ + private bool $opaqueShareConnectionCache = false; + + /** + * @param int $maxHandles Maximum number of idle handles. + * @param resource|\CurlShareHandle|\CurlSharePersistentHandle|CurlShareHandleState|null $shareHandle + */ + public function __construct(int $maxHandles, string $shareMode = TransportSharing::NONE, $shareHandle = null) { $this->maxHandles = $maxHandles; + $this->shareMode = CurlShareHandleState::normalizeMode($shareMode, 'transport_sharing'); + + if ($shareHandle instanceof CurlShareHandleState) { + if ($shareHandle->mode !== $this->shareMode) { + throw new InvalidArgumentException('The cURL share handle state mode does not match the configured transport sharing mode.'); + } + + // A Guzzle-created handler-lifetime state locks only DNS and TLS + // session data, so its handle can never own a connection cache. A + // persistent state's connection cache is worker-global, so other + // producers in the worker may populate it. + $this->opaqueShareConnectionCache = $shareHandle->mode === TransportSharing::PERSISTENT_PREFER + || $shareHandle->mode === TransportSharing::PERSISTENT_REQUIRE; + $shareHandle = $shareHandle->handle; + } elseif ($shareHandle !== null) { + // An externally supplied handle's cached contents cannot be + // inspected from PHP, so it may own a connection cache populated + // outside this factory. + $this->opaqueShareConnectionCache = true; + } + + if ($this->shareMode === TransportSharing::NONE && $shareHandle !== null) { + throw new InvalidArgumentException('A cURL share handle cannot be provided when transport sharing is disabled.'); + } + + if ($this->shareMode !== TransportSharing::NONE && $shareHandle === null) { + throw new InvalidArgumentException('A cURL share handle is required when transport sharing is enabled.'); + } + + if ($shareHandle !== null && !self::isCurlShareHandle($shareHandle)) { + throw new InvalidArgumentException('A cURL share handle must be an instance of CurlShareHandle, CurlSharePersistentHandle, or a curl_share resource.'); + } + + $this->shareHandle = $shareHandle; } - public function create(RequestInterface $request, array $options): EasyHandle + /** + * @param mixed $value + */ + private static function isCurlShareHandle($value): bool { + if (\PHP_VERSION_ID < 80000) { + return \is_resource($value) && \get_resource_type($value) === 'curl_share'; + } + + if ($value instanceof \CurlShareHandle) { + return true; + } + + return \class_exists('CurlSharePersistentHandle') + && $value instanceof \CurlSharePersistentHandle; + } + + public function create( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): EasyHandle { + $this->assertOpen(); + self::validateRequestUriScheme($request); + + $protocolVersion = $request->getProtocolVersion(); + + if ('' === $protocolVersion) { + throw new RequestException('HTTP protocol version must not be empty.', $request); + } + + if (1 !== \preg_match('/^\d+(?:\.\d+)?$/D', $protocolVersion)) { + throw new RequestException('HTTP protocol version must be a valid HTTP version number.', $request); + } + + CurlVersion::ensureSupported($request); + + $multiplex = self::normalizeMultiplex($options); + + if ('3' === $protocolVersion || '3.0' === $protocolVersion) { + if (!CurlVersion::supportsHttp3()) { + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new RequestException('Required multiplexing for HTTP/3 needs libcurl 8.13.0 or newer built with HTTP/3 support.', $request); + } + + throw new RequestException('HTTP/3 is supported by the cURL handler, however the installed PHP cURL extension or libcurl does not support HTTP/3.', $request); + } + } elseif ('2' === $protocolVersion || '2.0' === $protocolVersion) { + if (!CurlVersion::supportsHttp2()) { + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new RequestException('Required multiplexing needs libcurl 8.14.0 or newer built with HTTP/2 support.', $request); + } + + throw new RequestException('HTTP/2 is supported by the cURL handler, however libcurl 7.65.2 or newer built with HTTP/2 support is required.', $request); + } + } elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) { + throw new RequestException(sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request); + } + if (isset($options['curl']['body_as_string'])) { $options['_body_as_string'] = $options['curl']['body_as_string']; unset($options['curl']['body_as_string']); } - $easy = new EasyHandle; + self::rejectUnsupportedRequestOptions($options); + self::assertOnStatsCallable($options); + self::assertOnTrailersCallable($options); + $this->rejectRequestLevelShareConflict($options); + $this->rejectPersistentRequireConnectionReuseConflicts($options); + self::rejectUnsupportedCurlOptions($options); + self::rejectConflictingCurlOptions($options); + + $framing = RequestFraming::analyze($request, $request->getMethod() !== 'HEAD'); + $request = $framing->request; + + $managedProxyAuthorization = self::managedProxyAuthorizationHeaderLines($request); + + $easy = new EasyHandle(); $easy->request = $request; $easy->options = $options; $conf = $this->getDefaultConf($easy); - $this->applyMethod($easy, $conf); + $this->applyMethod($easy, $conf, $framing); $this->applyHandlerOptions($easy, $conf); $this->applyHeaders($easy, $conf); unset($conf['_headers']); @@ -65,31 +321,710 @@ class CurlFactory implements CurlFactoryInterface $conf = \array_replace($conf, $options['curl']); } + self::normalizeStringableProxyCredentialOptions($conf); + + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + self::assertRequiredMultiplexAuthSupported($conf); + } + + self::applyProxyConnectHeaderSuppression($request, $conf); + self::normalizeCurlHeaderOptions($conf); + self::applyProxyAuthorizationHeaderHandling($request, $conf, $managedProxyAuthorization); + // Validate the managed lines appended above too: a custom + // RequestInterface can return unvalidated header values. + self::normalizeCurlHeaderOptions($conf); + + if ($this->shareHandle !== null) { + // Conservative blanket mode: a configured share handle hides the + // pooled connections' provenance, so sectioned reuse cannot reason + // about them. + $this->forceFreshConnectionForAuthenticatedProxy($request, $conf); + } else { + $signature = self::proxyTunnelSignature($request, $conf); + $easy->proxyTunnelSignature = $signature; + if ($signature !== null && $signature !== $this->proxyTunnelOwner) { + if ($this->poolMayHoldTunnels) { + // Pooled idle handles may hold a different owner's tunnel. + $this->discardIdleHandles(); + $this->poolMayHoldTunnels = false; + } + // The first in-domain owner latches without purging: the pool + // provably holds no in-domain tunnel yet. + $this->proxyTunnelOwner = $signature; + } + } + + $easy->effectiveProxy = self::getEffectiveProxy($conf); + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); - $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init(); - curl_setopt_array($easy->handle, $conf); + if ($this->shareHandle !== null) { + if (!\defined('CURLOPT_SHARE')) { + throw new RequestException('The configured cURL share handle requires CURLOPT_SHARE, but it is not available in the installed PHP cURL extension.', $easy->request); + } + + $conf[(int) \constant('CURLOPT_SHARE')] = $this->shareHandle; + } + + if (\defined('CURLOPT_PIPEWAIT')) { + $easy->usesPipewait = !empty($conf[(int) \constant('CURLOPT_PIPEWAIT')]); + } + + $handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + if (false === $handle) { + throw new RequestException('Can not initialize cURL handle.', $request); + } + $easy->handle = $handle; + + try { + $this->applyCurlOptions($handle, $conf); + } catch (\Throwable $e) { + if (PHP_VERSION_ID < 80000 && \is_resource($handle)) { + \curl_close($handle); + } + unset($easy->handle); + + throw $e; + } return $easy; } - public function release(EasyHandle $easy): void + /** + * @param resource|\CurlHandle $handle + * @param array $conf + */ + private function applyCurlOptions( + #[\SensitiveParameter] + $handle, + #[\SensitiveParameter] + array $conf + ): void { + foreach ($conf as $option => $value) { + if (!\is_int($option)) { + throw new InvalidArgumentException(\sprintf( + 'Invalid cURL option %s.', + self::formatCurlOption($option) + )); + } + + try { + $success = curl_setopt($handle, $option, $value); + } catch (\Throwable $e) { + throw new InvalidArgumentException( + \sprintf( + 'Unable to set cURL option %s: %s', + self::formatCurlOption($option), + $e->getMessage() + ), + 0, + $e + ); + } + + if (!$success) { + throw new InvalidArgumentException(\sprintf( + 'Unable to set cURL option %s.', + self::formatCurlOption($option) + )); + } + } + } + + /** + * @param array $conf + */ + private static function normalizeStringableProxyCredentialOptions( + #[\SensitiveParameter] + array &$conf + ): void { + foreach (self::STRINGABLE_PROXY_CREDENTIAL_OPTIONS as $name) { + if (!\defined($name)) { + continue; + } + + $option = (int) \constant($name); + if (!isset($conf[$option]) || !\is_object($conf[$option]) || !\method_exists($conf[$option], '__toString')) { + continue; + } + + try { + $conf[$option] = (string) $conf[$option]; + } catch (\Throwable $e) { + // Wrap the failure exactly as applyCurlOptions() does for a + // value that cannot be applied. + throw new InvalidArgumentException( + \sprintf( + 'Unable to set cURL option %s: %s', + self::formatCurlOption($option), + $e->getMessage() + ), + 0, + $e + ); + } + } + } + + private function rejectRequestLevelShareConflict( + #[\SensitiveParameter] + array $options + ): void { + if ($this->shareHandle === null) { + return; + } + + if ( + !\defined('CURLOPT_SHARE') + || !isset($options['curl']) + || !\is_array($options['curl']) + || !\array_key_exists((int) \constant('CURLOPT_SHARE'), $options['curl']) + ) { + return; + } + + throw new InvalidArgumentException('The request-level CURLOPT_SHARE cURL option cannot be combined with configured transport sharing.'); + } + + private function rejectPersistentRequireConnectionReuseConflicts( + #[\SensitiveParameter] + array $options + ): void { + if ( + $this->shareMode !== TransportSharing::PERSISTENT_REQUIRE + || !isset($options['curl']) + || !\is_array($options['curl']) + ) { + return; + } + + if (!empty($options['curl'][\CURLOPT_FRESH_CONNECT])) { + throw new InvalidArgumentException('The CURLOPT_FRESH_CONNECT cURL option cannot be used when persistent cURL sharing is required because it disables connection reuse.'); + } + + if (!empty($options['curl'][\CURLOPT_FORBID_REUSE])) { + throw new InvalidArgumentException('The CURLOPT_FORBID_REUSE cURL option cannot be used when persistent cURL sharing is required because it disables connection reuse.'); + } + } + + private static function normalizeMultiplex( + #[\SensitiveParameter] + array $options + ): string { + $multiplex = $options['multiplex'] ?? null; + + if ($multiplex === null) { + return Multiplexing::WAIT; + } + + if (!\in_array($multiplex, [Multiplexing::NONE, Multiplexing::EAGER, Multiplexing::WAIT, Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new InvalidArgumentException(\sprintf( + 'The "multiplex" option must be null or a GuzzleHttp\\Multiplexing::* constant; received %s.', + \get_debug_type($multiplex) + )); + } + + return $multiplex; + } + + private static function assertRequiredMultiplexSupported( + #[\SensitiveParameter] + EasyHandle $easy + ): void { + if (!CurlVersion::supportsRequiredHttp2Multiplex()) { + throw new RequestException('Required multiplexing needs libcurl 8.14.0 or newer built with HTTP/2 support.', $easy->request); + } + + $proxy = ProxyEnv::resolveProxySelection($easy->request->getUri(), $easy->options['proxy'] ?? null); + self::assertSelectedProxySupported($proxy->getProxy(), $easy->request); + + if ('https' !== $easy->request->getUri()->getScheme() && $proxy->hasProxy()) { + throw new RequestException('Required multiplexing cannot be guaranteed for cleartext requests sent through a proxy.', $easy->request); + } + } + + /** + * libcurl forces NTLM-authenticated transfers onto HTTP/1.1: when the + * server picks NTLM from the offered mask, the connection is closed and the + * request is retried over HTTP/1.1 whatever HTTP version was asked for, + * silently defeating the required protocol guarantee on both cleartext and + * TLS routes. The final merged mask is checked because the raw + * CURLOPT_HTTPAUTH cURL option is allow-listed, and any mask permitting + * NTLM, such as CURLAUTH_ANY, is rejected because the selection is + * server-controlled. + * + * @param array $conf + */ + private static function assertRequiredMultiplexAuthSupported( + #[\SensitiveParameter] + array $conf + ): void { + if (!\array_key_exists(\CURLOPT_HTTPAUTH, $conf)) { + return; + } + + $auth = $conf[\CURLOPT_HTTPAUTH]; + if (!\is_scalar($auth)) { + throw new InvalidArgumentException('The "multiplex" request option cannot be required when the final CURLOPT_HTTPAUTH cURL option value is not an integer.'); + } + + $ntlmBits = \CURLAUTH_NTLM; + if (\defined('CURLAUTH_NTLM_WB')) { + $ntlmBits |= (int) \constant('CURLAUTH_NTLM_WB'); + } + + if (((int) $auth & $ntlmBits) !== 0) { + throw new InvalidArgumentException('The "multiplex" request option cannot be required when the final CURLOPT_HTTPAUTH cURL option value permits NTLM; libcurl retries NTLM authentication over HTTP/1.1.'); + } + } + + private static function assertSelectedProxySupported( + #[\SensitiveParameter] + ?string $selectedProxy, + #[\SensitiveParameter] + RequestInterface $request + ): void { + if ($selectedProxy === null || $selectedProxy === '') { + return; + } + + $scheme = ProxyOptions::proxyScheme($selectedProxy); + + if (!\in_array($scheme, ['http', 'https', 'socks4', 'socks4a', 'socks5', 'socks5h'], true)) { + throw new InvalidArgumentException(\sprintf('The "%s" proxy scheme is not supported by the cURL handler.', Psr7\DiagnosticValue::escape($scheme))); + } + + if ($scheme === 'https' && !CurlVersion::supportsHttpsProxy()) { + throw new RequestException('HTTPS proxies are not supported by the installed libcurl; libcurl 7.54.0 or newer built with HTTPS-proxy support is required.', $request); + } + } + + /** + * @param int|string $option + */ + private static function formatCurlOption($option): string { + if (!\is_int($option)) { + return \sprintf('"%s"', Psr7\DiagnosticValue::escape((string) $option)); + } + + static $names = null; + + if (null === $names) { + $names = []; + foreach (\get_defined_constants(true)['curl'] ?? [] as $name => $value) { + if (\is_int($value) && \strpos($name, 'CURLOPT_') === 0 && !isset($names[$value])) { + $names[$value] = $name; + } + } + } + + if (isset($names[$option])) { + return \sprintf('%s (%d)', $names[$option], $option); + } + + return (string) $option; + } + + private static function rejectConflictingCurlOptions( + #[\SensitiveParameter] + array $options + ): void { + if (!isset($options['curl']) || !\is_array($options['curl']) || $options['curl'] === []) { + return; + } + + $conflictingOptions = self::conflictingCurlOptions(); + + foreach ($options['curl'] as $option => $_) { + if (!\array_key_exists($option, $conflictingOptions)) { + continue; + } + + $name = self::formatCurlOption($option); + $replacement = $conflictingOptions[$option]; + if ($replacement !== null) { + throw new InvalidArgumentException(\sprintf( + 'Passing %s in the "curl" request option is not supported because it conflicts with Guzzle-managed request handling. Use %s instead.', + $name, + $replacement + )); + } + + throw new InvalidArgumentException(\sprintf( + 'Passing %s in the "curl" request option is not supported because it conflicts with Guzzle-managed cURL internals.', + $name + )); + } + } + + private static function rejectUnsupportedCurlOptions( + #[\SensitiveParameter] + array $options + ): void { + if (!isset($options['curl']) || !\is_array($options['curl']) || $options['curl'] === []) { + return; + } + + $supportedOptions = self::supportedCurlOptions(); + $conflictingOptions = self::conflictingCurlOptions(); + + foreach ($options['curl'] as $option => $_) { + if ( + !\is_int($option) + || \array_key_exists($option, $supportedOptions) + || \array_key_exists($option, $conflictingOptions) + ) { + continue; + } + + throw new InvalidArgumentException(\sprintf( + 'Passing %s in the "curl" request option is not supported because it is outside the built-in cURL handlers\' allow-list.', + self::formatCurlOption($option) + )); + } + } + + private static function rejectUnsupportedRequestOptions( + #[\SensitiveParameter] + array $options + ): void { + if (\array_key_exists('stream_context', $options)) { + throw new InvalidArgumentException('Passing the "stream_context" request option to a cURL handler is not supported because cURL handlers ignore PHP stream context options.'); + } + } + + private static function assertOnStatsCallable( + #[\SensitiveParameter] + array $options + ): void { + if (isset($options['on_stats']) && !\is_callable($options['on_stats'])) { + throw new InvalidArgumentException('on_stats must be callable'); + } + } + + private static function assertOnTrailersCallable( + #[\SensitiveParameter] + array $options + ): void { + if (isset($options['on_trailers']) && !\is_callable($options['on_trailers'])) { + throw new InvalidArgumentException('on_trailers must be callable'); + } + } + + /** + * @return array + */ + private static function conflictingCurlOptions(): array + { + static $options = null; + + if ($options !== null) { + return $options; + } + + $options = []; + + self::addConflictingCurlOption($options, 'CURLOPT_SHARE', 'the "transport_sharing" client option or cURL handler option'); + self::addConflictingCurlOption($options, 'CURLOPT_URL', 'the request URI'); + self::addConflictingCurlOption($options, 'CURLOPT_PORT', 'the request URI'); + self::addConflictingCurlOption($options, 'CURLOPT_CUSTOMREQUEST', 'the request method'); + self::addConflictingCurlOption($options, 'CURLOPT_HTTPGET', 'the request method'); + self::addConflictingCurlOption($options, 'CURLOPT_POST', 'the request method and body'); + self::addConflictingCurlOption($options, 'CURLOPT_PUT', 'the request method and body'); + self::addConflictingCurlOption($options, 'CURLOPT_NOBODY', 'the request method'); + self::addConflictingCurlOption($options, 'CURLOPT_UPLOAD', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_POSTFIELDS', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_READFUNCTION', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_SEEKFUNCTION', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_READDATA', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_INFILE', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_INFILESIZE', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_INFILESIZE_LARGE', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_HTTPHEADER', 'the request headers'); + self::addConflictingCurlOption($options, 'CURLOPT_USERAGENT', 'the request headers'); + self::addConflictingCurlOption($options, 'CURLOPT_REFERER', 'the request headers'); + self::addConflictingCurlOption($options, 'CURLOPT_HEADERFUNCTION', 'the "on_headers" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_WRITEFUNCTION', 'the "sink" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_FILE', 'the "sink" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_TIMEOUT', 'the "timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_TIMEOUT_MS', 'the "timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CONNECTTIMEOUT', 'the "connect_timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CONNECTTIMEOUT_MS', 'the "connect_timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_NOSIGNAL', 'the "timeout" or "connect_timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_NOPROGRESS', 'the "progress" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROGRESSFUNCTION', 'the "progress" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_XFERINFOFUNCTION', 'the "progress" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_VERBOSE', 'the "debug" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_STDERR', 'the "debug" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROXY', 'the "proxy" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_NOPROXY', 'the "proxy" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROXYTYPE', 'the "proxy" request option with a scheme-prefixed URL'); + self::addConflictingCurlOption($options, 'CURLOPT_FOLLOWLOCATION', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_MAXREDIRS', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_POSTREDIR', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_REDIR_PROTOCOLS', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_REDIR_PROTOCOLS_STR', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROTOCOLS', 'the "protocols" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROTOCOLS_STR', 'the "protocols" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_HTTP_VERSION', 'the request protocol version'); + self::addConflictingCurlOption($options, 'CURLOPT_PIPEWAIT', 'the "multiplex" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_IPRESOLVE', 'the "force_ip_resolve" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSL_VERIFYPEER', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSL_VERIFYHOST', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CAINFO', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CAPATH', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLVERSION', 'the "crypto_method" or "crypto_method_max" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLCERT', 'the "cert" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLCERTPASSWD', 'the "cert" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLCERTTYPE', 'the "cert_type" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLKEY', 'the "ssl_key" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLKEYPASSWD', 'the "ssl_key" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_KEYPASSWD', 'the "ssl_key" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLKEYTYPE', 'the "ssl_key_type" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIE', 'the "Cookie" request header or Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIEFILE', 'Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIEJAR', 'Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIELIST', 'Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIESESSION', 'Guzzle cookie middleware'); + + return $options; + } + + /** + * @return array + */ + private static function supportedCurlOptions(): array + { + static $options = null; + + if ($options !== null) { + return $options; + } + + $options = []; + + self::addSupportedCurlOption($options, 'CURLOPT_ADDRESS_SCOPE'); + self::addSupportedCurlOption($options, 'CURLOPT_CERTINFO'); + self::addSupportedCurlOption($options, 'CURLOPT_CONNECT_TO'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_CACHE_TIMEOUT'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_INTERFACE'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_LOCAL_IP4'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_LOCAL_IP6'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_SERVERS'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_SHUFFLE_ADDRESSES'); + self::addSupportedCurlOption($options, 'CURLOPT_ENCODING'); + self::addSupportedCurlOption($options, 'CURLOPT_FORBID_REUSE'); + self::addSupportedCurlOption($options, 'CURLOPT_FRESH_CONNECT'); + self::addSupportedCurlOption($options, 'CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS'); + self::addSupportedCurlOption($options, 'CURLOPT_HTTPAUTH'); + self::addSupportedCurlOption($options, 'CURLOPT_INTERFACE'); + self::addSupportedCurlOption($options, 'CURLOPT_LOCALPORT'); + self::addSupportedCurlOption($options, 'CURLOPT_LOCALPORTRANGE'); + self::addSupportedCurlOption($options, 'CURLOPT_LOW_SPEED_LIMIT'); + self::addSupportedCurlOption($options, 'CURLOPT_LOW_SPEED_TIME'); + self::addSupportedCurlOption($options, 'CURLOPT_MAXAGE_CONN'); + self::addSupportedCurlOption($options, 'CURLOPT_MAXCONNECTS'); + self::addSupportedCurlOption($options, 'CURLOPT_MAXLIFETIME_CONN'); + self::addSupportedCurlOption($options, 'CURLOPT_HTTPPROXYTUNNEL'); + self::addSupportedCurlOption($options, 'CURLOPT_PREREQFUNCTION'); + self::addSupportedCurlOption($options, 'CURLOPT_PROXYHEADER'); + self::addSupportedCurlOption($options, 'CURLOPT_PROXYUSERPWD'); + self::addSupportedCurlOption($options, 'CURLOPT_RESOLVE'); + self::addSupportedCurlOption($options, 'CURLOPT_SSL_CIPHER_LIST'); + self::addSupportedCurlOption($options, 'CURLOPT_SSL_EC_CURVES'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_FASTOPEN'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPALIVE'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPIDLE'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPINTVL'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPCNT'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_NODELAY'); + self::addSupportedCurlOption($options, 'CURLOPT_TLS13_CIPHERS'); + self::addSupportedCurlOption($options, 'CURLOPT_UNIX_SOCKET_PATH'); + self::addSupportedCurlOption($options, 'CURLOPT_USERPWD'); + + return $options; + } + + /** + * @param array $options + */ + private static function addSupportedCurlOption(array &$options, string $constant): void + { + if (!\defined($constant)) { + return; + } + + $value = \constant($constant); + if (\is_int($value)) { + $options[$value] = true; + } + } + + /** + * @param array $options + */ + private static function addConflictingCurlOption(array &$options, string $constant, ?string $replacement): void + { + if (!\defined($constant)) { + return; + } + + $value = \constant($constant); + if (\is_int($value)) { + $options[$value] = $replacement; + } + } + + private static function validateRequestUriScheme( + #[\SensitiveParameter] + RequestInterface $request + ): void { + $scheme = $request->getUri()->getScheme(); + if ($scheme === '') { + throw new RequestException('URI must include a scheme and host. Use an absolute URI, a network-path reference starting with //, or configure a base_uri.', $request); + } + + if (!\in_array($scheme, ['http', 'https'], true)) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", Psr7\DiagnosticValue::escape($scheme)), $request); + } + } + + public function release( + #[\SensitiveParameter] + EasyHandle $easy + ): void { + $this->assertOpen(); + $resource = $easy->handle; unset($easy->handle); - if (\count($this->handles) >= $this->maxHandles) { - \curl_close($resource); - } else { - // Remove all callback functions as they can hold onto references - // and are not cleaned up by curl_reset. Using curl_setopt_array - // does not work for some reason, so removing each one - // individually. - \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null); - \curl_setopt($resource, \CURLOPT_READFUNCTION, null); - \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null); - \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null); - \curl_reset($resource); - $this->handles[] = $resource; + if ( + \count($this->handles) >= $this->maxHandles + || ($easy->proxyTunnelSignature !== null && $easy->proxyTunnelSignature !== $this->proxyTunnelOwner) + ) { + // Pool is full, or this handle belongs to a superseded tunnel + // owner (an async create/release overlap can hand a stale-owner + // handle back after a purge) - drop it instead of pooling it. + $this->discardHandle($resource); + + return; + } + + if ($easy->proxyTunnelSignature !== null) { + // A pooled handle now carries the current owner's tunnel. + $this->poolMayHoldTunnels = true; + } + + // Remove all callback functions as they can hold onto references and + // are not cleaned up by curl_reset. Using curl_setopt_array does not + // work for some reason, so removing each one individually. + $this->clearEasyHandleCallbacks($resource); + \curl_reset($resource); + $this->handles[] = $resource; + } + + /** + * Closes idle cURL handles owned by this factory. + * + * After closing, the factory is terminal and must not be reused. + */ + public function close(): void + { + $this->doClose(true); + } + + private function assertOpen(): void + { + if ($this->closed) { + // Programmer misuse (reusing a closed factory), not a transfer failure; + // intentionally a LogicException outside the GuzzleException hierarchy. + throw new \BadMethodCallException('Cannot use the cURL factory after it has been closed.'); + } + } + + private function doClose(bool $explicit): void + { + if ($this->closed) { + return; + } + + $this->closed = true; + $failure = null; + + try { + foreach ($this->handles as $id => $handle) { + try { + $this->discardHandle($handle); + } catch (\Throwable $e) { + if ($failure === null) { + $failure = $e; + } + } finally { + unset($this->handles[$id]); + } + } + } finally { + $this->shareMode = TransportSharing::NONE; + $this->shareHandle = null; + } + + if ($explicit && $failure !== null) { + throw $failure; + } + } + + /** + * @param resource|\CurlHandle $handle + */ + private function discardHandle( + #[\SensitiveParameter] + $handle + ): void { + $failure = null; + + try { + $this->clearEasyHandleCallbacks($handle); + } catch (\Throwable $e) { + $failure = $e; + } + + try { + if (PHP_VERSION_ID < 80000 && \is_resource($handle)) { + \curl_close($handle); + } + } catch (\Throwable $e) { + if ($failure === null) { + $failure = $e; + } + } + + if ($failure !== null) { + throw $failure; + } + } + + /** + * @param resource|\CurlHandle $handle + */ + private function clearEasyHandleCallbacks( + #[\SensitiveParameter] + $handle + ): void { + curl_setopt($handle, \CURLOPT_HEADERFUNCTION, null); + curl_setopt($handle, \CURLOPT_READFUNCTION, null); + curl_setopt($handle, \CURLOPT_WRITEFUNCTION, null); + curl_setopt($handle, \CURLOPT_PROGRESSFUNCTION, null); + + if (\defined('CURLOPT_PREREQFUNCTION')) { + curl_setopt($handle, (int) \constant('CURLOPT_PREREQFUNCTION'), null); + } + + if (\defined('CURLOPT_SEEKFUNCTION')) { + curl_setopt($handle, (int) \constant('CURLOPT_SEEKFUNCTION'), null); + } + + if (\defined('CURLOPT_XFERINFOFUNCTION')) { + curl_setopt($handle, (int) \constant('CURLOPT_XFERINFOFUNCTION'), null); } } @@ -97,169 +1032,1273 @@ class CurlFactory implements CurlFactoryInterface * Completes a cURL transaction, either returning a response promise or a * rejected promise. * - * @param callable(RequestInterface, array): PromiseInterface $handler - * @param CurlFactoryInterface $factory Dictates how the handle is released + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + * + * @return PromiseInterface */ - public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface - { - if (isset($easy->options['on_stats'])) { - self::invokeStats($easy); + public static function finish( + callable $handler, + #[\SensitiveParameter] + EasyHandle $easy, + CurlFactoryInterface $factory + ): PromiseInterface { + /** @var (callable(TransferStats): mixed)|null $onStats */ + $onStats = $easy->options['on_stats'] ?? null; + $stats = $onStats !== null ? self::createStats($easy) : null; + + if (self::shouldFinishWithError($easy)) { + return self::finishError($handler, $easy, $factory, $stats, $onStats); } - if (!$easy->response || $easy->errno) { - return self::finishError($handler, $easy, $factory); - } + /** @var ResponseInterface $response */ + $response = $easy->response; // Return the response if it is present and there is no error. $factory->release($easy); - // Rewind the body of the response if possible. - $body = $easy->response->getBody(); - if ($body->isSeekable()) { - $body->rewind(); + // Rewind the body of the response if possible. Failures here are local + // response finalization errors, not response-transfer failures. + $body = $response->getBody(); + try { + if ($body->isSeekable()) { + $body->rewind(); + } + } catch (\Exception $e) { + $reason = new ResponseException( + $e->getMessage() !== '' ? $e->getMessage() : 'Failed to rewind the response body', + $easy->request, + $response, + $e + ); + + if ($onStats !== null && $stats !== null) { + // Report the ResponseException rather than errno 0 to match the + // stream handler's response finalization stats. + $onStats(new TransferStats( + $easy->request, + $response, + $stats->getTransferTime(), + $reason, + $stats->getHandlerStats() + )); + } + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); } - return new FulfilledPromise($easy->response); + if (isset($easy->options['on_trailers'])) { + /** @var callable(array>, ResponseInterface, RequestInterface): mixed $onTrailers */ + $onTrailers = $easy->options['on_trailers']; + + try { + $onTrailers(self::headersFromTrailerLines($easy->trailers), $response, $easy->request); + } catch (\Throwable $e) { + $reason = new ResponseException( + 'An error was encountered during the on_trailers event', + $easy->request, + $response, + $e + ); + + if ($onStats !== null && $stats !== null) { + // Report the ResponseException rather than errno 0 to match + // the response finalization stats above. + $onStats(new TransferStats( + $easy->request, + $response, + $stats->getTransferTime(), + $reason, + $stats->getHandlerStats() + )); + } + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); + } + } + + if ($onStats !== null && $stats !== null) { + $onStats($stats); + } + + /** @var PromiseInterface */ + return P\Create::promiseFor($response); } - private static function invokeStats(EasyHandle $easy): void - { + private static function createStats( + #[\SensitiveParameter] + EasyHandle $easy + ): TransferStats { $curlStats = \curl_getinfo($easy->handle); $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); - $stats = new TransferStats( + + if ($easy->createResponseException) { + $curlStats = [ + 'total_time' => $curlStats['total_time'], + 'appconnect_time' => $curlStats['appconnect_time'], + ]; + } + + $handlerErrorData = $easy->responseHeaderException ?? $easy->responseBodySizeException ?? $easy->errno; + + return new TransferStats( $easy->request, $easy->response, $curlStats['total_time'], - $easy->errno, + $handlerErrorData, $curlStats ); - ($easy->options['on_stats'])($stats); } /** - * @param callable(RequestInterface, array): PromiseInterface $handler + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param (callable(TransferStats): mixed)|null $onStats + * + * @return PromiseInterface */ - private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface - { + private static function finishError( + callable $handler, + #[\SensitiveParameter] + EasyHandle $easy, + CurlFactoryInterface $factory, + ?TransferStats $stats, + ?callable $onStats + ): PromiseInterface { // Get error information and release the handle to the factory. - $ctx = [ - 'errno' => $easy->errno, - 'error' => \curl_error($easy->handle), - 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME), - ] + \curl_getinfo($easy->handle); - $ctx[self::CURL_VERSION_STR] = \curl_version()['version']; + $ctx = self::createErrorContext($easy); + + if (self::shouldRetryFailedRewind($easy)) { + // Release after dispatching the retry so the replacement + // transfer cannot reuse this native handle ID. + try { + if ($onStats !== null && $stats !== null) { + $onStats($stats); + } + + return self::retryFailedRewind($handler, $easy, $ctx); + } finally { + $factory->release($easy); + } + } + $factory->release($easy); - // Retry when nothing is present or when curl failed to rewind. - if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) { - return self::retryFailedRewind($handler, $easy, $ctx); + if ($onStats !== null && $stats !== null) { + $onStats($stats); + } + + if (self::isResponseRewindFailure($easy)) { + $status = $easy->response !== null ? $easy->response->getStatusCode() : 0; + $ctx['error'] = $status === 401 || $status === 407 + ? 'The server issued an authentication challenge ' + .'after the request body had already been sent, and the body ' + .'could not be rewound to resend it. The request was not ' + .'retried because a retry replays the same challenge. See ' + .'https://bugs.php.net/bug.php?id=47204 for more information.' + : 'The server responded after the request body had already ' + .'been sent, and the body could not be rewound for ' + .'libcurl\'s automatic retry. The request was not retried ' + .'because a retry replays the same response. See ' + .'https://bugs.php.net/bug.php?id=47204 for more information.'; } return self::createRejection($easy, $ctx); } - private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface + private static function shouldFinishWithError(EasyHandle $easy): bool { - static $connectionErrors = [ - \CURLE_OPERATION_TIMEOUTED => true, - \CURLE_COULDNT_RESOLVE_HOST => true, - \CURLE_COULDNT_CONNECT => true, - \CURLE_SSL_CONNECT_ERROR => true, - \CURLE_GOT_NOTHING => true, - ]; + return !$easy->response + || $easy->errno !== 0 + || self::hasLocalFailure($easy); + } + private static function hasLocalFailure(EasyHandle $easy): bool + { + return $easy->bodyReadTimeoutException !== null + || $easy->bodyRewindTimeoutException !== null + || $easy->bodyRewindException !== null + || $easy->bodyReadException !== null + || $easy->responseHeaderException !== null + || $easy->sinkWriteTimeoutException !== null + || $easy->sinkWriteException !== null + || $easy->sinkWriteIncomplete + || $easy->responseBodySizeException !== null; + } + + private static function shouldRetryFailedRewind(EasyHandle $easy): bool + { + if (self::hasLocalFailure($easy)) { + return false; + } + + if (self::isResponseRewindFailure($easy)) { + // Re-issuing the identical request replays the same response and + // the same in-transfer rewind, so retrying is futile and the + // received response is surfaced instead. + return false; + } + + // Two transfer outcomes warrant rewinding the body and retrying: + // + // - errno === CURLE_SEND_FAIL_REWIND (65): libcurl needed to rewind an + // already-partially-sent upload to resend it on a reused connection + // that died before any response arrived, but could not. PHP builds + // without CURLOPT_SEEKFUNCTION register no seek callback for a + // streamed request body (https://bugs.php.net/bug.php?id=47204); + // builds with it can still report 65 when the body is not seekable. + // + // - errno === 0: libcurl reported success yet no usable response + // reached us. This is the legacy curl_multi silent-failure variant of + // the same rewind problem. libcurl 7.61.1 fixed it to surface as + // CURLE_SEND_FAIL_REWIND instead (curl/curl@d6cf930), so this arm is + // only load-bearing for libcurl < 7.61.1 and may be removed once the + // minimum supported libcurl is >= 7.61.1. + return $easy->errno === 0 || $easy->errno === self::CURLE_SEND_FAIL_REWIND; + } + + /** + * Whether the transfer failed because libcurl could not rewind the + * request body to resend it in reply to a response, such as a 401 or + * 407 during multi-pass authentication, or a 417 that makes libcurl + * retry an Expect: 100-continue upload without the expectation. Rewinds + * for replays that follow no response, such as a reused connection that + * died or a refused HTTP/2 stream, retry through + * shouldRetryFailedRewind() instead. Followed redirects also rewind, + * but the built-in handlers never enable them. + */ + private static function isResponseRewindFailure(EasyHandle $easy): bool + { + return $easy->errno === self::CURLE_SEND_FAIL_REWIND + && $easy->response !== null + && !self::hasLocalFailure($easy); + } + + private static function createErrorContext(EasyHandle $easy): array + { + return [ + 'errno' => $easy->errno, + 'error' => \curl_error($easy->handle), + ]; + } + + /** + * @return PromiseInterface + */ + private static function createRejection( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array $ctx, + #[\SensitiveParameter] + ?\Throwable $previous = null + ): PromiseInterface { if ($easy->createResponseException) { + /** @var PromiseInterface */ return P\Create::rejectionFor( new RequestException( 'An error was encountered while creating the response', $easy->request, - $easy->response, - $easy->createResponseException, - $ctx + 0, + $easy->createResponseException ) ); } + if ($easy->responseHeaderException) { + /** @var PromiseInterface */ + return P\Create::rejectionFor($easy->responseHeaderException); + } + // If an exception was encountered during the onHeaders event, then // return a rejected promise that wraps that exception. if ($easy->onHeadersException) { - return P\Create::rejectionFor( - new RequestException( - 'An error was encountered during the on_headers event', - $easy->request, - $easy->response, - $easy->onHeadersException, - $ctx - ) + return self::createRequestOrResponseRejection( + $easy, + 'An error was encountered during the on_headers event', + $easy->onHeadersException ); } + if ($easy->progressException) { + return self::createRequestOrResponseRejection( + $easy, + 'An error was encountered during the progress event', + $easy->progressException + ); + } + + if ($easy->bodyReadTimeoutException) { + // Reading the request body stalled, which is a caller-stream failure. + return self::createRequestOrResponseRejection( + $easy, + 'Timed out while reading the request body', + $easy->bodyReadTimeoutException + ); + } + + if ($easy->bodyRewindTimeoutException) { + // Rewinding the request body stalled, which is a caller-stream failure. + return self::createRequestOrResponseRejection( + $easy, + 'Timed out while rewinding the request body', + $easy->bodyRewindTimeoutException + ); + } + + if ($easy->bodyRewindException) { + $message = $easy->bodyRewindException->getMessage() !== '' + ? $easy->bodyRewindException->getMessage() + : 'Failed to rewind the request body'; + + return self::createRequestOrResponseRejection($easy, $message, $easy->bodyRewindException); + } + + if ($easy->bodyReadException) { + $message = $easy->bodyReadException->getMessage() !== '' + ? $easy->bodyReadException->getMessage() + : 'Failed to read the request body'; + + return self::createRequestOrResponseRejection($easy, $message, $easy->bodyReadException); + } + + if ($easy->sinkWriteTimeoutException) { + // Writing the response body to the caller's sink stalled, which is a + // caller-stream failure. + return self::createRequestOrResponseRejection( + $easy, + 'Timed out while writing the response body', + $easy->sinkWriteTimeoutException + ); + } + + if ($easy->sinkWriteException) { + $message = $easy->sinkWriteException->getMessage() !== '' + ? $easy->sinkWriteException->getMessage() + : 'Failed to write the response body'; + + return self::createRequestOrResponseRejection($easy, $message, $easy->sinkWriteException); + } + + if ($easy->sinkWriteIncomplete) { + return self::createRequestOrResponseRejection($easy, 'Unable to write to stream'); + } + + if ($easy->responseBodySizeException) { + return self::createRequestOrResponseRejection( + $easy, + $easy->responseBodySizeException->getMessage(), + $easy->responseBodySizeException + ); + } + + if ($easy->progressAborted && $easy->errno === \CURLE_ABORTED_BY_CALLBACK) { + return self::createRequestOrResponseRejection( + $easy, + 'The transfer was aborted by the progress callback' + ); + } + + $uri = $easy->request->getUri(); + + $sanitizedError = self::sanitizeCurlError($ctx['error'] ?? '', $uri, $easy->effectiveProxy); + $message = \sprintf( 'cURL error %s: %s (%s)', $ctx['errno'], - $ctx['error'], - 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + $sanitizedError, + 'see https://curl.se/libcurl/c/libcurl-errors.html' ); - $uriString = (string) $easy->request->getUri(); - if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { - $message .= \sprintf(' for %s', $uriString); + + if ('' !== $sanitizedError) { + $safeUri = Psr7\Utils::redactUriForMessage($uri); + if ($safeUri !== '' && false === \strpos($sanitizedError, $safeUri)) { + $message .= \sprintf(' for %s', $safeUri); + } } - // Create a connection exception if it was a specific error code. - $error = isset($connectionErrors[$easy->errno]) - ? new ConnectException($message, $easy->request, null, $ctx) - : new RequestException($message, $easy->request, $easy->response, null, $ctx); + if ($easy->errno === \CURLE_OPERATION_TIMEOUTED) { + if ($easy->response !== null) { + $error = new ResponseTimeoutException($message, $easy->request, $easy->response, $previous); + } elseif (self::isConnectTimeoutError($ctx['error'] ?? '')) { + $error = new ConnectTimeoutException($message, $easy->request, $previous); + } else { + $error = new NetworkTimeoutException($message, $easy->request, $previous); + } + } elseif ($easy->response) { + $error = self::isResponseTransferError($easy->errno) + ? new ResponseTransferException($message, $easy->request, $easy->response, $previous) + : new ResponseException($message, $easy->request, $easy->response, $previous); + } elseif (self::isConnectionError($easy->errno)) { + $error = new ConnectException($message, $easy->request, $previous); + } elseif (self::isNetworkError($easy->errno)) { + $error = new NetworkException($message, $easy->request, $previous); + } else { + $error = new RequestException($message, $easy->request, 0, $previous); + } + /** @var PromiseInterface */ return P\Create::rejectionFor($error); } + /** + * @return PromiseInterface + */ + private static function createRequestOrResponseRejection( + #[\SensitiveParameter] + EasyHandle $easy, + string $message, + #[\SensitiveParameter] + ?\Throwable $previous = null + ): PromiseInterface { + if ($easy->response !== null) { + /** @var PromiseInterface */ + return P\Create::rejectionFor( + new ResponseException($message, $easy->request, $easy->response, $previous) + ); + } + + /** @var PromiseInterface */ + return P\Create::rejectionFor( + new RequestException($message, $easy->request, 0, $previous) + ); + } + + private static function isConnectionError(int $errno): bool + { + return isset(self::CURL_CONNECTION_ERRORS[$errno]); + } + + private static function isNetworkError(int $errno): bool + { + return isset(self::CURL_NETWORK_ERRORS[$errno]); + } + + private static function isResponseTransferError(int $errno): bool + { + return self::isConnectionError($errno) + || self::isNetworkError($errno) + || isset(self::CURL_RESPONSE_TRANSFER_ERRORS[$errno]); + } + + private static function isConnectTimeoutError(string $error): bool + { + if ('' === $error) { + return false; + } + + foreach (self::CURL_CONNECT_TIMEOUT_ERRORS as $connectTimeoutError) { + if (Psr7\Utils::caselessContains($error, $connectTimeoutError)) { + return true; + } + } + + return Psr7\Utils::caselessContains($error, 'Failed to resolve') + && Psr7\Utils::caselessContains($error, 'timeout'); + } + + private static function sanitizeCurlError( + #[\SensitiveParameter] + string $error, + #[\SensitiveParameter] + UriInterface $uri, + #[\SensitiveParameter] + ?string $proxy = null + ): string { + if ('' === $error) { + return $error; + } + + $error = self::redactProxyUserInfo($error, $proxy); + + return UriDiagnostic::redactInMessage($error, $uri); + } + + private static function redactProxyUserInfo( + #[\SensitiveParameter] + string $error, + #[\SensitiveParameter] + ?string $proxy + ): string { + if ($proxy === null || $proxy === '') { + return $error; + } + + return Psr7\Utils::redactUserInfoInString($error, $proxy); + } + + /** + * @param array $conf + */ + private function forceFreshConnectionForAuthenticatedProxy( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$conf + ): void { + $proxy = self::getEffectiveProxy($conf); + + if ( + $proxy === null + || (!self::requiresFreshConnectionForAuthenticatedProxy($request, $proxy, $conf) + && !$this->isOpaqueShareAnonymousProxyTunnel($request, $proxy, $conf)) + ) { + return; + } + + if ($this->shareMode === TransportSharing::PERSISTENT_REQUIRE) { + throw new InvalidArgumentException(self::PERSISTENT_REQUIRE_FRESH_PROXY_TUNNEL_MESSAGE); + } + + $conf[\CURLOPT_FRESH_CONNECT] = true; + $conf[\CURLOPT_FORBID_REUSE] = true; + } + + /** + * @param array $conf + */ + private function isOpaqueShareAnonymousProxyTunnel(RequestInterface $request, string $proxy, array $conf): bool + { + if (!$this->opaqueShareConnectionCache || !CurlVersion::supportsShareConnectionCaches()) { + return false; + } + + if (!self::usesProxyTunnel($request, $conf) || !self::isHttpProxyForConnectionReuse($proxy, $conf)) { + return false; + } + + if ( + self::hasCurlProxyAuthorizationHeader($conf) + || self::hasCurlProxyTlsCredentials($conf) + || self::hasCurlProxyCredentials($conf) + ) { + return false; + } + + $proxyForParsing = \strpos($proxy, '://') === false ? 'http://'.$proxy : $proxy; + $proxyParts = \parse_url($proxyForParsing); + if ( + \is_array($proxyParts) + && (\array_key_exists('user', $proxyParts) || \array_key_exists('pass', $proxyParts)) + ) { + return false; + } + + // From libcurl 7.57.0 an opaque share handle can own a connection + // cache, and a tunnel seeded there with a literal Proxy-Authorization + // header is never keyed on credentials, so an anonymous request could + // inherit it on every later libcurl version. Requests carrying + // recognized credential state keep the version-gated channel + // safeguards in requiresFreshConnectionForAuthenticatedProxy(). + return true; + } + + /** + * @param array $conf + */ + private static function getEffectiveProxy(array $conf): ?string + { + if (!\array_key_exists(\CURLOPT_PROXY, $conf)) { + return null; + } + + $proxy = $conf[\CURLOPT_PROXY]; + + return \is_string($proxy) && $proxy !== '' ? $proxy : null; + } + + /** + * @param array $conf + */ + private static function normalizeCurlHeaderOptions( + #[\SensitiveParameter] + array &$conf + ): void { + $options = [\CURLOPT_HTTPHEADER => 'CURLOPT_HTTPHEADER']; + if (\defined('CURLOPT_PROXYHEADER')) { + $options[(int) \constant('CURLOPT_PROXYHEADER')] = 'CURLOPT_PROXYHEADER'; + } + + foreach ($options as $option => $label) { + if (!\array_key_exists($option, $conf) || !\is_array($conf[$option])) { + continue; + } + + $normalized = []; + foreach ($conf[$option] as $key => $entry) { + if (\is_object($entry) && \method_exists($entry, '__toString')) { + $entry = (string) $entry; + } elseif (!\is_string($entry)) { + throw new InvalidArgumentException(\sprintf('%s entries must be strings or stringable objects.', $label)); + } + + if (\strpbrk($entry, "\r\n") !== false) { + throw new InvalidArgumentException(\sprintf('%s entries must not contain a carriage return or line feed.', $label)); + } + + $normalized[$key] = $entry; + } + + $conf[$option] = $normalized; + } + } + + /** + * @param array $conf + */ + private static function requiresFreshConnectionForAuthenticatedProxy(RequestInterface $request, string $proxy, array $conf): bool + { + // SOCKS authentication binds an identity to the connection itself, and + // below 7.69.0 an opaque configured share may already contain a SOCKS + // connection whose credential state Guzzle cannot inspect. Isolate + // authenticated and anonymous requests so neither can inherit it. + if (self::isSocksProxy($proxy, $conf)) { + return !CurlVersion::supportsSocksProxyCredentialAwareConnectionReuse(); + } + + if (!self::usesProxyTunnel($request, $conf) || !self::isHttpProxyForConnectionReuse($proxy, $conf)) { + return false; + } + + $proxyForParsing = \strpos($proxy, '://') === false ? 'http://'.$proxy : $proxy; + $proxyParts = \parse_url($proxyForParsing); + if (!\is_array($proxyParts)) { + return false; + } + + if (self::hasCurlProxyAuthorizationHeader($conf)) { + return true; + } + + // A proxy client certificate or TLS-SRP authenticates the client to the + // HTTPS proxy at the TLS layer; libcurl ignored TLS-SRP before 7.83.1 + // (CVE-2022-27782), so an old build can reuse a tunnel across those + // identities. Force a fresh one, as the non-share signature path does. + // See docs/contributing/curl-connection-reuse.md. + if ( + !CurlVersion::supportsProxyTlsCredentialAwareConnectionReuse() + && self::hasCurlProxyTlsCredentials($conf) + ) { + return true; + } + + if (CurlVersion::supportsProxyCredentialAwareConnectionReuse()) { + return false; + } + + return \array_key_exists('user', $proxyParts) + || \array_key_exists('pass', $proxyParts) + || self::hasCurlProxyCredentials($conf); + } + + /** + * @param array $conf + */ + private static function applyProxyConnectHeaderSuppression( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$conf + ): void { + $proxy = $conf[\CURLOPT_PROXY] ?? null; + + if (!\is_string($proxy) + || $proxy === '' + || self::isSocksProxy($proxy, $conf) + || !self::usesProxyTunnel($request, $conf) + ) { + return; + } + + if (!CurlVersion::supportsProxyTunneling()) { + throw new RequestException('Tunneling requests through an HTTP proxy is not supported by the installed libcurl; libcurl 7.54.0 or newer is required.', $request); + } + + // Keep the proxy CONNECT reply out of the header callback so a + // tunnelled transfer failure is not misclassified as a response + // failure carrying the proxy's interim 200. + $conf[(int) \constant('CURLOPT_SUPPRESS_CONNECT_HEADERS')] = true; + } + + /** + * @param array $conf + */ + private static function usesProxyTunnel(RequestInterface $request, array $conf): bool + { + $scheme = $request->getUri()->getScheme(); + + if ('https' === $scheme) { + return true; + } + + // An HTTP proxy auto-switches to a CONNECT tunnel when CONNECT_TO + // redirects the origin, so an http:// target with it set tunnels too. + if ('http' === $scheme && self::hasCurlConnectTo($conf)) { + return true; + } + + return \defined('CURLOPT_HTTPPROXYTUNNEL') + && \array_key_exists((int) \constant('CURLOPT_HTTPPROXYTUNNEL'), $conf) + && (bool) $conf[(int) \constant('CURLOPT_HTTPPROXYTUNNEL')]; + } + + /** + * @param array $conf + */ + private static function hasCurlConnectTo(array $conf): bool + { + if (!\defined('CURLOPT_CONNECT_TO')) { + return false; + } + + $option = (int) \constant('CURLOPT_CONNECT_TO'); + if (!\array_key_exists($option, $conf)) { + return false; + } + + $value = $conf[$option]; + + return \is_array($value) + ? $value !== [] + : $value !== null && $value !== false && $value !== ''; + } + + /** + * @param array $conf + */ + private static function isHttpProxyForConnectionReuse(string $proxy, array $conf): bool + { + if (\strpos($proxy, '://') !== false) { + $proxyParts = \parse_url($proxy); + if (!\is_array($proxyParts) || !isset($proxyParts['scheme'])) { + return false; + } + + $proxyScheme = Psr7\Utils::asciiToLower($proxyParts['scheme']); + + return $proxyScheme === 'http' || $proxyScheme === 'https'; + } + + return !self::isSocksProxyType($conf[\CURLOPT_PROXYTYPE] ?? null); + } + + private static function proxyScheme(string $proxy): ?string + { + $position = \strpos($proxy, '://'); + + return $position === false ? null : Psr7\Utils::asciiToLower(\substr($proxy, 0, $position)); + } + + /** + * @param array $conf + */ + private static function isSocksProxy(string $proxy, array $conf): bool + { + $scheme = self::proxyScheme($proxy); + if ($scheme !== null) { + if (\in_array($scheme, ['socks', 'socks4', 'socks4a', 'socks5', 'socks5h'], true)) { + return true; + } + + // libcurl preserves a raw SOCKS CURLOPT_PROXYTYPE behind an http + // scheme, while every other scheme overrides the proxy type. + if ($scheme !== 'http') { + return false; + } + } + + return self::isSocksProxyType($conf[\CURLOPT_PROXYTYPE] ?? null); + } + + /** + * Computes the connection-reuse section signature for a SOCKS proxy. + * libcurl compares SOCKS credentials on connection reuse from 7.69.0 (curl + * #4835), so no sectioning is needed there. Older libcurl matches a SOCKS + * proxy by type, host, and port only, so every SOCKS request is sectioned + * by its credential state; hashing the credential-less state too keeps an + * unauthenticated request from inheriting an authenticated connection. See + * docs/contributing/curl-connection-reuse.md. + * + * @param array $conf + */ + private static function socksProxySignature( + #[\SensitiveParameter] + string $proxy, + #[\SensitiveParameter] + array $conf + ): ?string { + if (CurlVersion::supportsSocksProxyCredentialAwareConnectionReuse()) { + return null; + } + + $credentialState = []; + foreach (['CURLOPT_PROXYUSERPWD', 'CURLOPT_PROXYUSERNAME', 'CURLOPT_PROXYPASSWORD', 'CURLOPT_PROXYTYPE'] as $name) { + $credentialState[$name] = \defined($name) + ? ($conf[(int) \constant($name)] ?? null) + : null; + } + + return \hash('sha256', \serialize(['socks', $proxy, $credentialState])); + } + + /** + * @param mixed $proxyType + */ + private static function isSocksProxyType($proxyType): bool + { + if (!\is_int($proxyType)) { + return false; + } + + foreach ([ + 'CURLPROXY_SOCKS4' => 4, + 'CURLPROXY_SOCKS5' => 5, + 'CURLPROXY_SOCKS4A' => 6, + 'CURLPROXY_SOCKS5_HOSTNAME' => 7, + ] as $name => $fallback) { + $value = \defined($name) ? (int) \constant($name) : $fallback; + if ($proxyType === $value) { + return true; + } + } + + return false; + } + + /** + * @param array $conf + */ + private static function hasCurlProxyCredentials(array $conf): bool + { + foreach (['CURLOPT_PROXYUSERPWD', 'CURLOPT_PROXYUSERNAME', 'CURLOPT_PROXYPASSWORD'] as $option) { + if (\defined($option) && \array_key_exists((int) \constant($option), $conf)) { + return true; + } + } + + return false; + } + + /** + * @param array $conf + */ + private static function hasCurlProxyTlsCredentials(array $conf): bool + { + foreach ([ + 'CURLOPT_PROXY_SSLCERT', + 'CURLOPT_PROXY_SSLCERT_BLOB', + 'CURLOPT_PROXY_TLSAUTH_USERNAME', + 'CURLOPT_PROXY_TLSAUTH_PASSWORD', + ] as $option) { + if (\defined($option) && \array_key_exists((int) \constant($option), $conf)) { + return true; + } + } + + return false; + } + + /** + * @param array $conf + */ + private static function hasCurlProxyAuthorizationHeader(array $conf): bool + { + return self::curlProxyAuthorizationHeaderValues($conf) !== []; + } + + /** + * Collects the first-class Proxy-Authorization request header lines that + * Guzzle configures in cURL's proxy-only header channel. Empty values use + * cURL's semicolon form so they suppress an automatically generated proxy + * authorization field without carrying a credential. getHeaderLine() is + * deliberately not used: comma-joining multiple credentials would change + * their wire representation and connection signature. + * + * @return list + */ + private static function managedProxyAuthorizationHeaderLines( + #[\SensitiveParameter] + RequestInterface $request + ): array { + $headers = []; + + foreach ($request->getHeader('Proxy-Authorization') as $value) { + $headers[] = $value === '' + ? 'Proxy-Authorization;' + : 'Proxy-Authorization: '.$value; + } + + return $headers; + } + + /** + * Routes the managed Proxy-Authorization lines and any caller-provided + * proxy headers through cURL's proxy-only header channel without + * consulting Guzzle's route prediction, so no proxy-classification + * discrepancy can disclose them to an origin server. libcurl uses the + * proxy-only list only for HTTP requests it actually sends to a proxy, so + * direct, no-proxy-bypassed, and SOCKS transfers never deliver it. + * + * @param array $conf + * @param list $managedHeaders + */ + private static function applyProxyAuthorizationHeaderHandling( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$conf, + #[\SensitiveParameter] + array $managedHeaders + ): void { + if ($managedHeaders !== [] || self::hasCurlProxyHeaderOption($conf)) { + if (!CurlVersion::supportsProxyHeaderSeparation()) { + throw new RequestException('Proxy headers require libcurl 7.37.0 or newer built with proxy header separation support.', $request); + } + + if ($managedHeaders !== []) { + self::appendCurlProxyHeaders($conf, $managedHeaders); + } + + $conf[(int) \constant('CURLOPT_HEADEROPT')] = (int) \constant('CURLHEADER_SEPARATE'); + } + + // Keep the conservative CONNECT header-list separation for effective + // HTTP(S) proxy tunnels without a proxy header: on libcurl + // 7.37.0-7.42.0 the default is CURLHEADER_UNIFIED. This broad route + // approximation never decides managed credential routing. + $proxy = self::getEffectiveProxy($conf); + if ( + CurlVersion::supportsProxyHeaderSeparation() + && $proxy !== null + && self::isHttpProxyForConnectionReuse($proxy, $conf) + && self::usesProxyTunnel($request, $conf) + ) { + $conf[(int) \constant('CURLOPT_HEADEROPT')] = (int) \constant('CURLHEADER_SEPARATE'); + } + } + + /** + * @param array $conf + * @param list $headers + */ + private static function appendCurlProxyHeaders( + #[\SensitiveParameter] + array &$conf, + #[\SensitiveParameter] + array $headers + ): void { + $option = (int) \constant('CURLOPT_PROXYHEADER'); + + if (\array_key_exists($option, $conf)) { + if (!\is_array($conf[$option])) { + throw new InvalidArgumentException('CURLOPT_PROXYHEADER must be an array when a Proxy-Authorization request header is routed to the proxy header channel.'); + } + + $headers = \array_merge($conf[$option], $headers); + } + + $conf[$option] = $headers; + } + + /** + * @param array $conf + */ + private static function hasCurlProxyHeaderOption(array $conf): bool + { + return \defined('CURLOPT_PROXYHEADER') + && \array_key_exists((int) \constant('CURLOPT_PROXYHEADER'), $conf); + } + + /** + * @param mixed[] $headers + * + * @return list + */ + private static function proxyAuthorizationHeaderValuesFromList(array $headers): array + { + $values = []; + + foreach ($headers as $header) { + if (!\is_string($header)) { + continue; + } + + $position = \strpos($header, ':'); + if ($position === false) { + continue; + } + + if (!Psr7\Utils::caselessEquals(\trim(\substr($header, 0, $position), " \n\r\t\0\x0B"), 'Proxy-Authorization')) { + continue; + } + + $value = \trim(\substr($header, $position + 1), " \n\r\t\0\x0B"); + if ($value !== '') { + $values[] = $value; + } + } + + return $values; + } + + /** + * Computes the connection-reuse section signature for a proxy tunnel or + * SOCKS proxy, or null when the request does not require sectioning. + * + * @param array $conf + */ + private static function proxyTunnelSignature( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $conf + ): ?string { + $proxy = self::getEffectiveProxy($conf); + if ($proxy === null) { + return null; + } + + // SOCKS authentication binds an identity to the connection itself, for + // plain http:// requests as much as https://, so it sections ahead of + // the CONNECT tunnel domain checks. + if (self::isSocksProxy($proxy, $conf)) { + return self::socksProxySignature($proxy, $conf); + } + + if ( + !self::usesProxyTunnel($request, $conf) + || !self::isHttpProxyForConnectionReuse($proxy, $conf) + ) { + return null; + } + + $headerAuth = self::curlProxyAuthorizationHeaderValues($conf); + if ($headerAuth === [] && CurlVersion::supportsProxyCredentialAwareConnectionReuse()) { + // libcurl keys reuse on parsed proxy credentials only from 8.19.0, + // trusted from 8.20.0 (PROXY_CREDENTIAL_REUSE_VERSION); a literal + // Proxy-Authorization header is never keyed and always sections. + // See docs/contributing/curl-connection-reuse.md. + return self::DELEGATED_PROXY_TUNNEL_OWNER; + } + + // Hash every proxy channel an old libcurl might not key reuse on. A + // changed signature only forces a fresh connection, never relaxes + // reuse, so over-covering is always safe; under-covering leaks. Proxy + // credentials are the channel CVE-2026-3784 missed; the proxy-TLS + // options are load-bearing on builds before the proxy-TLS reuse fixes + // (the proxy client cert is keyed from 7.52.0, libcurl's first + // HTTPS-proxy release; CVE-2016-5420 (7.50.1) is only the origin-cert + // precedent; TLS-SRP from 7.83.1, CVE-2022-27782) and harmless after. + // The private-key file and passphrase are hashed on this non-delegated + // path too, as fallback hardening: libcurl's mTLS private-key matching + // on reuse was incomplete before 8.21.0 (CVE-2026-8932). This does not + // cover the delegated path (the early return above) or configured share + // handles, so it is not a complete pre-8.21.0 mitigation. The key blob + // and cert/key type encodings (PROXY_SSLKEY_BLOB, PROXY_SSLKEYTYPE, + // PROXY_SSLCERTTYPE) are not hashed and are an accepted residual. See + // docs/contributing/curl-connection-reuse.md. + $credentialState = []; + foreach ([ + 'CURLOPT_PROXYUSERPWD', 'CURLOPT_PROXYUSERNAME', 'CURLOPT_PROXYPASSWORD', + 'CURLOPT_PROXYTYPE', + 'CURLOPT_PROXY_SSLCERT', 'CURLOPT_PROXY_SSLCERT_BLOB', 'CURLOPT_PROXY_SSLKEY', + 'CURLOPT_PROXY_KEYPASSWD', 'CURLOPT_PROXY_TLSAUTH_USERNAME', + 'CURLOPT_PROXY_TLSAUTH_PASSWORD', 'CURLOPT_PROXY_SSLVERSION', + ] as $name) { + $credentialState[$name] = \defined($name) + ? ($conf[(int) \constant($name)] ?? null) + : null; + } + + return \hash('sha256', \serialize([$proxy, $credentialState, $headerAuth])); + } + + /** + * @param array $conf + * + * @return list + */ + private static function curlProxyAuthorizationHeaderValues(array $conf): array + { + if (!\defined('CURLOPT_PROXYHEADER')) { + return []; + } + + $option = (int) \constant('CURLOPT_PROXYHEADER'); + if (!\array_key_exists($option, $conf)) { + return []; + } + + $headers = $conf[$option]; + if (!\is_array($headers)) { + return []; + } + + return self::proxyAuthorizationHeaderValuesFromList($headers); + } + + private function discardIdleHandles(): void + { + foreach ($this->handles as $id => $handle) { + try { + // Best effort: a cleanup hiccup on a superseded idle handle + // must not abort the unrelated request that triggered the purge. + $this->discardHandle($handle); + } catch (\Throwable $e) { + // Ignored. + } finally { + unset($this->handles[$id]); + } + } + } + /** * @return array */ - private function getDefaultConf(EasyHandle $easy): array - { + private function getDefaultConf( + #[\SensitiveParameter] + EasyHandle $easy + ): array { + $uri = $easy->request->getUri(); + $protocols = Utils::normalizeProtocols($easy->options['protocols'] ?? ['http', 'https']); + $scheme = $uri->getScheme(); + if (!\in_array($scheme, $protocols, true)) { + throw new RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', Psr7\DiagnosticValue::escape($scheme)), $easy->request); + } + + if ($uri->getHost() === '') { + throw new RequestException('URI must include a scheme and host. Use an absolute URI, a network-path reference starting with //, or configure a base_uri.', $easy->request); + } + $conf = [ - '_headers' => $easy->request->getHeaders(), - \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), - \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + '_headers' => $easy->request->getHeaders(), + \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + \CURLOPT_URL => (string) $uri->withFragment(''), \CURLOPT_RETURNTRANSFER => false, - \CURLOPT_HEADER => false, - \CURLOPT_CONNECTTIMEOUT => 150, + \CURLOPT_HEADER => false, + \CURLOPT_CONNECTTIMEOUT_MS => self::DEFAULT_CONNECT_TIMEOUT_MS, ]; - if (\defined('CURLOPT_PROTOCOLS')) { - $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS; + if (CurlVersion::supportsProtocolsStr()) { + $conf[(int) \constant('CURLOPT_PROTOCOLS_STR')] = \implode(',', $protocols); + } else { + $conf[\CURLOPT_PROTOCOLS] = self::curlProtocolMask($protocols); } $version = $easy->request->getProtocolVersion(); - if ($version == 1.1) { + $multiplex = self::normalizeMultiplex($easy->options); + + if ('3' === $version || '3.0' === $version) { + if (!\defined('CURL_HTTP_VERSION_3')) { + throw new RequestException('HTTP/3 is not supported by this cURL installation.', $easy->request); + } + + $proxy = ProxyEnv::resolveProxySelection($easy->request->getUri(), $easy->options['proxy'] ?? null); + + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + self::assertSelectedProxySupported($proxy->getProxy(), $easy->request); + + if ($proxy->hasProxy()) { + throw new RequestException('Required multiplexing cannot be guaranteed for HTTP/3 requests sent through a proxy.', $easy->request); + } + if (!CurlVersion::supportsRequiredHttp3Multiplex()) { + throw new RequestException('Required multiplexing for HTTP/3 needs libcurl 8.13.0 or newer built with HTTP/3 support.', $easy->request); + } + // HTTP/3 or fail: required multiplexing never downgrades, not + // even to HTTP/2. + $conf[\CURLOPT_HTTP_VERSION] = (int) \constant('CURL_HTTP_VERSION_3ONLY'); + } else { + $conf[\CURLOPT_HTTP_VERSION] = $proxy->hasProxy() + ? (CurlVersion::supportsHttp2() ? \CURL_HTTP_VERSION_2_0 : \CURL_HTTP_VERSION_1_1) + : (int) \constant('CURL_HTTP_VERSION_3'); + } + } elseif ('2' === $version || '2.0' === $version) { + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + self::assertRequiredMultiplexSupported($easy); + // New HTTP/2 connections cannot negotiate HTTP/1.x here, and + // the 8.14.0 floor's version-aware reuse matching keeps + // reused connections on HTTP/2 as well. + $conf[\CURLOPT_HTTP_VERSION] = (int) \constant('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'); + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } + } elseif ('1.1' === $version) { + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new RequestException(\sprintf('The "multiplex" request option cannot be required for HTTP/%s requests; use protocol version 2 or 3.', $version), $easy->request); + } $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; - } elseif ($version == 2.0) { - $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; } else { + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new RequestException(\sprintf('The "multiplex" request option cannot be required for HTTP/%s requests; use protocol version 2 or 3.', $version), $easy->request); + } $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; } + $resolvedVersion = $conf[\CURLOPT_HTTP_VERSION]; + if (\in_array($multiplex, [Multiplexing::WAIT, Multiplexing::REQUIRE_WAIT], true) + && CurlVersion::supportsMultiplex() + && ($resolvedVersion === \CURL_HTTP_VERSION_2_0 + || (\defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE') && $resolvedVersion === (int) \constant('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) + || (\defined('CURL_HTTP_VERSION_3') && $resolvedVersion === (int) \constant('CURL_HTTP_VERSION_3')) + || (\defined('CURL_HTTP_VERSION_3ONLY') && $resolvedVersion === (int) \constant('CURL_HTTP_VERSION_3ONLY'))) + ) { + // Wait for an in-progress connection to the same origin to reveal + // whether it can be multiplexed instead of opening another one. + $conf[(int) \constant('CURLOPT_PIPEWAIT')] = true; + } + return $conf; } - private function applyMethod(EasyHandle $easy, array &$conf): void + /** + * @param string[] $protocols + */ + private static function curlProtocolMask(array $protocols): int { - $body = $easy->request->getBody(); - $size = $body->getSize(); + $mask = 0; - if ($size === null || $size > 0) { - $this->applyBody($easy->request, $easy->options, $conf); - return; + if (\in_array('http', $protocols, true)) { + $mask |= \CURLPROTO_HTTP; } - $method = $easy->request->getMethod(); - if ($method === 'PUT' || $method === 'POST') { - // See https://tools.ietf.org/html/rfc7230#section-3.3.2 - if (!$easy->request->hasHeader('Content-Length')) { - $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; - } - } elseif ($method === 'HEAD') { + if (\in_array('https', $protocols, true)) { + $mask |= \CURLPROTO_HTTPS; + } + + return $mask; + } + + /** + * @param mixed $type + */ + private static function normalizeTlsFileType(string $option, $type): string + { + if (!\is_string($type) || $type === '') { + throw new InvalidArgumentException(\sprintf('%s must be a non-empty string', $option)); + } + + return Psr7\Utils::asciiToUpper($type); + } + + private static function shouldValidateSslKeyFile(?string $type): bool + { + return $type !== 'ENG' && $type !== 'PROV'; + } + + private function applyMethod( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array &$conf, + RequestFraming $framing + ): void { + if ($easy->request->getMethod() === 'HEAD') { + // libcurl stops at HEAD response headers only when CURLOPT_NOBODY + // is set; CURLOPT_CUSTOMREQUEST changes only the method string. + // NOBODY also suppresses request upload, so strip non-zero body + // length, transfer coding, and a 100-continue expectation. + $conf[\CURLOPT_CUSTOMREQUEST] = null; $conf[\CURLOPT_NOBODY] = true; unset( $conf[\CURLOPT_WRITEFUNCTION], @@ -267,35 +2306,162 @@ class CurlFactory implements CurlFactoryInterface $conf[\CURLOPT_FILE], $conf[\CURLOPT_INFILE] ); + if (\defined('CURLOPT_SEEKFUNCTION')) { + unset($conf[(int) \constant('CURLOPT_SEEKFUNCTION')]); + } + if (\trim($easy->request->getHeaderLine('Content-Length'), " \n\r\t\0\x0B") !== '0') { + $this->removeHeader('Content-Length', $conf); + } + $this->removeHeader('Transfer-Encoding', $conf); + if (Psr7\Utils::caselessEquals(\trim($easy->request->getHeaderLine('Expect'), " \n\r\t\0\x0B"), '100-continue')) { + $this->removeHeader('Expect', $conf); + } + + return; + } + + if ($framing->bodySize !== 0 && $framing->contentLength !== 0) { + $this->applyBody($easy, $conf, $framing); } } - private function applyBody(RequestInterface $request, array $options, array &$conf): void - { - $size = $request->hasHeader('Content-Length') - ? (int) $request->getHeaderLine('Content-Length') - : null; + private function applyBody( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array &$conf, + RequestFraming $framing + ): void { + $request = $easy->request; + $options = $easy->options; + $contentLength = $framing->contentLength; // Send the body as a string if the size is less than 1MB OR if the // [curl][body_as_string] request value is set. - if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) { - $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody(); + if (($framing->bodySize !== null && $framing->bodySize < 1000000) || !empty($options['_body_as_string'])) { + $conf[\CURLOPT_POSTFIELDS] = $framing->materialize(); // Don't duplicate the Content-Length header $this->removeHeader('Content-Length', $conf); $this->removeHeader('Transfer-Encoding', $conf); } else { $conf[\CURLOPT_UPLOAD] = true; - if ($size !== null) { - $conf[\CURLOPT_INFILESIZE] = $size; + + if ($contentLength !== null) { + // Never let cURL emit our header; it sizes the upload via a cURL input-size option. $this->removeHeader('Content-Length', $conf); + $conf[self::curlInputSizeOption($request, $contentLength)] = $contentLength; } + $body = $request->getBody(); - if ($body->isSeekable()) { - $body->rewind(); + try { + if ($body->isSeekable()) { + $body->rewind(); + } + } catch (\Exception $e) { + $message = $e instanceof TimeoutException + ? 'Timed out while rewinding the request body' + : ($e->getMessage() !== '' ? $e->getMessage() : 'Failed to rewind the request body'); + + throw new RequestException($message, $request, 0, $e); } - $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { - return $body->read($length); + /** + * @return int|string + */ + $remaining = $contentLength; + $conf[\CURLOPT_READFUNCTION] = static function ( + #[\SensitiveParameter] + $ch, + #[\SensitiveParameter] + $fd, + int $length + ) use ($easy, $body, &$remaining) { + if ($remaining === 0) { + return ''; + } + + $limit = $remaining === null ? $length : \min($length, $remaining); + + try { + $data = $body->read($limit); + } catch (TimeoutException $e) { + $easy->bodyReadTimeoutException = $e; + + return self::CURL_READFUNC_ABORT; + } catch (\Throwable $e) { + $easy->bodyReadException = $e; + + return self::CURL_READFUNC_ABORT; + } + + $dataLength = \strlen($data); + if ($dataLength > $limit) { + $easy->bodyReadException = new \RuntimeException('Request body stream returned more bytes than requested'); + + return self::CURL_READFUNC_ABORT; + } + + if ($remaining !== null) { + if ($data === '') { + $easy->bodyReadException = new \RuntimeException('Request body ended before the declared Content-Length was reached'); + + return self::CURL_READFUNC_ABORT; + } + + $remaining -= $dataLength; + } + + return $data; }; + if (\defined('CURLOPT_SEEKFUNCTION')) { + // Give libcurl a seek callback so it can reposition the + // streamed upload itself when a transfer must resend the body + // (multi-pass auth such as NTLM/Negotiate, or a reused + // connection that died) instead of failing with + // CURLE_SEND_FAIL_REWIND. See + // https://bugs.php.net/bug.php?id=47204. A failed seek is + // recorded so the rejection carries the caller's exception + // and the failed-rewind retry does not fire. + $successfulSeeks = 0; + $conf[(int) \constant('CURLOPT_SEEKFUNCTION')] = static function ( + #[\SensitiveParameter] + $ch, + int $offset, + int $origin + ) use ($easy, $body, $contentLength, &$remaining, &$successfulSeeks): int { + if ($origin !== \SEEK_SET) { + return self::CURL_SEEKFUNC_CANTSEEK; + } + + try { + if (!$body->isSeekable()) { + return self::CURL_SEEKFUNC_CANTSEEK; + } + + if ($offset < 0 || ($contentLength !== null && $offset > $contentLength)) { + throw new \RuntimeException('Request body seek offset is outside the declared Content-Length'); + } + + if ($successfulSeeks >= self::CURL_SEEK_MAX_REPLAYS) { + throw new \RuntimeException(\sprintf('Request body cannot be replayed more than %d times', self::CURL_SEEK_MAX_REPLAYS)); + } + + $body->seek($offset, \SEEK_SET); + } catch (TimeoutException $e) { + $easy->bodyRewindTimeoutException = $e; + + return self::CURL_SEEKFUNC_FAIL; + } catch (\Throwable $e) { + $easy->bodyRewindException = $e; + + return self::CURL_SEEKFUNC_FAIL; + } + + $remaining = $contentLength === null ? null : $contentLength - $offset; + ++$successfulSeeks; + + return self::CURL_SEEKFUNC_OK; + }; + } } // If the Expect header is not present, prevent curl from adding it @@ -309,9 +2475,37 @@ class CurlFactory implements CurlFactoryInterface } } - private function applyHeaders(EasyHandle $easy, array &$conf): void - { + private static function curlInputSizeOption( + #[\SensitiveParameter] + RequestInterface $request, + int $contentLength + ): int { + $option = \defined('CURLOPT_INFILESIZE_LARGE') + ? (int) \constant('CURLOPT_INFILESIZE_LARGE') + : \CURLOPT_INFILESIZE; + + // cURL's long remains 32-bit on 64-bit Windows. + if (\PHP_OS_FAMILY === 'Windows' && $option === \CURLOPT_INFILESIZE && $contentLength > 2147483647) { + throw new RequestException('Content-Length exceeds the maximum cURL upload size supported by this PHP build', $request); + } + + return $option; + } + + private function applyHeaders( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array &$conf + ): void { foreach ($conf['_headers'] as $name => $values) { + // The managed Proxy-Authorization field never enters the origin + // header list; applyProxyAuthorizationHeaderHandling() routes the + // values through cURL's proxy-only header channel. + if (Psr7\Utils::caselessEquals((string) $name, 'Proxy-Authorization')) { + continue; + } + foreach ($values as $value) { $value = (string) $value; if ($value === '') { @@ -339,15 +2533,49 @@ class CurlFactory implements CurlFactoryInterface private function removeHeader(string $name, array &$options): void { foreach (\array_keys($options['_headers']) as $key) { - if (!\strcasecmp($key, $name)) { + if (Psr7\Utils::caselessEquals((string) $key, $name)) { unset($options['_headers'][$key]); + return; } } } - private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + /** + * Decorates a caller-owned sink stream so that closing the response body + * detaches Guzzle's wrapper without closing the original PHP resource. + */ + private static function streamForResourceSink(StreamInterface $stream): StreamInterface { + return FnStream::decorate($stream, [ + 'close' => static function () use ($stream): void { + $stream->detach(); + }, + ]); + } + + /** + * @param mixed $factory + */ + private static function requireStreamFactory($factory): StreamFactoryInterface + { + if (!$factory instanceof StreamFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::STREAM_FACTORY, + StreamFactoryInterface::class + )); + } + + return $factory; + } + + private function applyHandlerOptions( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array &$conf + ): void { $options = $easy->options; if (isset($options['verify'])) { if ($options['verify'] === false) { @@ -360,16 +2588,16 @@ class CurlFactory implements CurlFactoryInterface if (\is_string($options['verify'])) { // Throw an error if the file/folder/link path is not valid or doesn't exist. if (!\file_exists($options['verify'])) { - throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}"); + throw new InvalidArgumentException(\sprintf('SSL CA bundle not found: %s', Psr7\DiagnosticValue::escape($options['verify']))); } // If it's a directory or a link to a directory use CURLOPT_CAPATH. // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. if ( - \is_dir($options['verify']) || - ( - \is_link($options['verify']) === true && - ($verifyLink = \readlink($options['verify'])) !== false && - \is_dir($verifyLink) + \is_dir($options['verify']) + || ( + \is_link($options['verify']) === true + && ($verifyLink = \readlink($options['verify'])) !== false + && \is_dir($verifyLink) ) ) { $conf[\CURLOPT_CAPATH] = $options['verify']; @@ -380,42 +2608,85 @@ class CurlFactory implements CurlFactoryInterface } } - if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + if (!isset($options['curl'][\CURLOPT_ENCODING]) && isset($options['decode_content']) && $options['decode_content'] !== false) { $accept = $easy->request->getHeaderLine('Accept-Encoding'); - if ($accept) { + if ($accept !== '') { $conf[\CURLOPT_ENCODING] = $accept; } else { // The empty string enables all available decoders and implicitly // sets a matching 'Accept-Encoding' header. $conf[\CURLOPT_ENCODING] = ''; - // But as the user did not specify any acceptable encodings we need - // to overwrite this implicit header with an empty one. + // But as the user did not specify any encoding preference, + // let's leave it up to server by preventing curl from sending + // the header, which will be interpreted as 'Accept-Encoding: *'. + // https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; } } - if (!isset($options['sink'])) { + $streamFactory = self::requireStreamFactory($options[RequestOptions::STREAM_FACTORY] ?? new HttpFactory()); + $hasSink = isset($options['sink']); + if (!$hasSink) { // Use a default temp stream if no sink was set. - $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + $options['sink'] = Psr7\Utils::tryFopen('php://temp', 'w+'); } $sink = $options['sink']; - if (!\is_string($sink)) { - $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink); + if ($hasSink && \is_resource($sink)) { + $sink = self::streamForResourceSink(Psr7\Utils::streamFor($sink)); + } elseif (\is_resource($sink)) { + $sink = $streamFactory->createStreamFromResource($sink); + } elseif (!\is_string($sink)) { + $sink = Psr7\Utils::streamFor($sink); } elseif (!\is_dir(\dirname($sink))) { // Ensure that the directory exists before failing in curl. - throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink)); + throw new RequestException(\sprintf('Directory %s does not exist for sink value of %s', Psr7\DiagnosticValue::escape(\dirname($sink)), Psr7\DiagnosticValue::escape($sink)), $easy->request); } else { $sink = new LazyOpenStream($sink, 'w+'); } $easy->sink = $sink; - $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int { - return $sink->write($write); + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, string $write) use ($easy, $sink): int { + $length = \strlen($write); + + try { + $newResponseBodyBytes = TransferByteCounter::add( + $easy->responseBodyBytes, + $length, + 'Response body exceeds the maximum integer size supported on this platform' + ); + } catch (\OverflowException $e) { + $easy->responseBodySizeException = $e; + + return 0; + } + + try { + $written = $sink->write($write); + } catch (TimeoutException $e) { + $easy->sinkWriteTimeoutException = $e; + + return 0; + } catch (\Throwable $e) { + $easy->sinkWriteException = $e; + + return 0; + } + + if ($written !== $length) { + $easy->sinkWriteIncomplete = true; + + return 0; + } + + $easy->responseBodyBytes = $newResponseBodyBytes; + + return $written; }; $timeoutRequiresNoSignal = false; if (isset($options['timeout'])) { - $timeoutRequiresNoSignal |= $options['timeout'] < 1; - $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + $timeout = Timeout::toMilliseconds($options['timeout'], 'timeout'); + $timeoutRequiresNoSignal |= $timeout > 0 && $timeout < 1000; + $conf[\CURLOPT_TIMEOUT_MS] = $timeout; } // CURL default value is CURL_IPRESOLVE_WHATEVER @@ -428,72 +2699,160 @@ class CurlFactory implements CurlFactoryInterface } if (isset($options['connect_timeout'])) { - $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; - $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + $connectTimeout = Timeout::toMilliseconds($options['connect_timeout'], 'connect_timeout'); + if ($connectTimeout > 0) { + $timeoutRequiresNoSignal |= $connectTimeout < 1000; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $connectTimeout; + } else { + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = self::CONNECT_TIMEOUT_DISABLED_MS; + } } - if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + if ($timeoutRequiresNoSignal && \PHP_OS_FAMILY !== 'Windows') { $conf[\CURLOPT_NOSIGNAL] = true; } - if (isset($options['proxy'])) { - if (!\is_array($options['proxy'])) { - $conf[\CURLOPT_PROXY] = $options['proxy']; - } else { - $scheme = $easy->request->getUri()->getScheme(); - if (isset($options['proxy'][$scheme])) { - $host = $easy->request->getUri()->getHost(); - if (!isset($options['proxy']['no']) || !Utils::isHostInNoProxy($host, $options['proxy']['no'])) { - $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme]; - } - } - } + // Always pin CURLOPT_PROXY and CURLOPT_NOPROXY so that libcurl never + // falls back to reading proxy environment variables itself. + $proxy = ProxyEnv::resolveProxySelection($easy->request->getUri(), $options['proxy'] ?? null); + $selectedProxy = $proxy->getProxy(); + if ($selectedProxy !== null) { + // Validate the whole proxy URL up front (ProxyOptions leans on + // Psr7\Uri), so a malformed proxy fails the same way on every + // handler, then check the scheme against what libcurl can use. + self::assertSelectedProxySupported($selectedProxy, $easy->request); + + $conf[\CURLOPT_PROXY] = $selectedProxy; + $conf[\CURLOPT_NOPROXY] = ''; + } else { + $conf[\CURLOPT_PROXY] = ''; + $conf[\CURLOPT_NOPROXY] = $proxy->isBypassed() ? '*' : ''; + } + + $this->applyTlsVersionRange($easy, $conf); + + $certType = null; + if (isset($options['cert_type'])) { + $certType = self::normalizeTlsFileType('cert_type', $options['cert_type']); + $conf[\CURLOPT_SSLCERTTYPE] = $certType; } if (isset($options['cert'])) { $cert = $options['cert']; if (\is_array($cert)) { - $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + if (!isset($cert[0]) || !\is_string($cert[0])) { + throw new InvalidArgumentException('Invalid cert request option'); + } + if (isset($cert[1])) { + if (!\is_string($cert[1])) { + throw new InvalidArgumentException('Invalid cert request option'); + } + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + } $cert = $cert[0]; } - if (!\file_exists($cert)) { - throw new \InvalidArgumentException("SSL certificate not found: {$cert}"); + if (!\is_string($cert)) { + throw new InvalidArgumentException('Invalid cert request option'); } - # OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. - # see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + if (!\file_exists($cert)) { + throw new InvalidArgumentException(\sprintf('SSL certificate not found: %s', Psr7\DiagnosticValue::escape($cert))); + } + // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html $ext = pathinfo($cert, \PATHINFO_EXTENSION); - if (preg_match('#^(der|p12)$#i', $ext)) { - $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext); + if ($certType === null && preg_match('#^(der|p12)$#iD', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = Psr7\Utils::asciiToUpper($ext); } $conf[\CURLOPT_SSLCERT] = $cert; } + $sslKeyType = null; + if (isset($options['ssl_key_type'])) { + $sslKeyType = self::normalizeTlsFileType('ssl_key_type', $options['ssl_key_type']); + $conf[\CURLOPT_SSLKEYTYPE] = $sslKeyType; + } + if (isset($options['ssl_key'])) { if (\is_array($options['ssl_key'])) { - if (\count($options['ssl_key']) === 2) { - [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key']; - } else { - [$sslKey] = $options['ssl_key']; + if (!isset($options['ssl_key'][0]) || !\is_string($options['ssl_key'][0])) { + throw new InvalidArgumentException('Invalid ssl_key request option'); } + if (isset($options['ssl_key'][1])) { + if (!\is_string($options['ssl_key'][1])) { + throw new InvalidArgumentException('Invalid ssl_key request option'); + } + $conf[\CURLOPT_SSLKEYPASSWD] = $options['ssl_key'][1]; + } + $sslKey = $options['ssl_key'][0]; } $sslKey = $sslKey ?? $options['ssl_key']; - if (!\file_exists($sslKey)) { - throw new \InvalidArgumentException("SSL private key not found: {$sslKey}"); + if (!\is_string($sslKey)) { + throw new InvalidArgumentException('Invalid ssl_key request option'); + } + + if (self::shouldValidateSslKeyFile($sslKeyType) && !\file_exists($sslKey)) { + throw new InvalidArgumentException(\sprintf('SSL private key not found: %s', Psr7\DiagnosticValue::escape($sslKey))); } $conf[\CURLOPT_SSLKEY] = $sslKey; } - if (isset($options['progress'])) { - $progress = $options['progress']; - if (!\is_callable($progress)) { - throw new \InvalidArgumentException('progress client option must be callable'); - } + $progress = $options['progress'] ?? null; + if ($progress !== null && !\is_callable($progress)) { + throw new InvalidArgumentException('progress client option must be callable'); + } + + // The streaming read callback (set by applyBody) aborts the upload on a + // body read failure by returning CURL_READFUNC_ABORT, but PHP ignores + // that integer return before 8.1.17/8.2.4. Install a progress callback + // so older PHP still has a cross-version abort path; the failure is + // classified from the stored request-body exception regardless of errno + // (a truncated request may reach the server first on those versions). + $abortsOnBodyReadFailure = isset($conf[\CURLOPT_READFUNCTION]); + + if ($progress !== null || $abortsOnBodyReadFailure) { + /** @var (callable(int, int, int, int): mixed)|null $progress */ $conf[\CURLOPT_NOPROGRESS] = false; - $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) { - $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + $progressCallback = static function ($resource, $downloadSize, $downloaded, $uploadSize, $uploaded) use ($easy, $progress): int { + // Abort the transfer when the request body read or rewind + // failed (the cross-version abort path, since older PHP + // ignores the read callback's return). progressAborted is left + // unset so the failure is classified from the stored + // request-body exception. + if ($easy->bodyReadTimeoutException !== null || $easy->bodyRewindTimeoutException !== null || $easy->bodyRewindException !== null || $easy->bodyReadException !== null) { + return 1; + } + + if ($progress === null) { + return 0; + } + + try { + if ($progress( + TransferByteCounter::progressValueToInt($downloadSize), + TransferByteCounter::progressValueToInt($downloaded), + TransferByteCounter::progressValueToInt($uploadSize), + TransferByteCounter::progressValueToInt($uploaded) + )) { + $easy->progressAborted = true; + + return 1; + } + + return 0; + } catch (\Throwable $e) { + $easy->progressException = $e; + + return 1; + } }; + + if (\defined('CURLOPT_XFERINFOFUNCTION')) { + $conf[(int) \constant('CURLOPT_XFERINFOFUNCTION')] = $progressCallback; + } else { + $conf[\CURLOPT_PROGRESSFUNCTION] = $progressCallback; + } } if (!empty($options['debug'])) { @@ -502,6 +2861,122 @@ class CurlFactory implements CurlFactoryInterface } } + private function applyTlsVersionRange( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array &$conf + ): void { + $options = $easy->options; + $cryptoMethod = $options['crypto_method'] ?? null; + $cryptoMethodMax = $options['crypto_method_max'] ?? null; + + if ($cryptoMethod === null && 'https' === $easy->request->getUri()->getScheme()) { + $cryptoMethod = \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + + if ($cryptoMethod === null && $cryptoMethodMax === null) { + return; + } + + $protocolVersion = $easy->request->getProtocolVersion(); + $isHttp3 = '3' === $protocolVersion || '3.0' === $protocolVersion; + $isHttp2 = '2' === $protocolVersion || '2.0' === $protocolVersion; + + if (($isHttp2 || $isHttp3) && $cryptoMethodMax !== null && TlsVersion::ordinal('crypto_method_max', $cryptoMethodMax) < 12) { + throw new InvalidArgumentException( + 'Invalid crypto_method_max request option: HTTP/2 and HTTP/3 require TLS 1.2 or higher' + ); + } + + if (($isHttp2 || $isHttp3) && $cryptoMethod !== null && TlsVersion::ordinal('crypto_method', $cryptoMethod) < 12) { + $cryptoMethod = \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + + TlsVersion::assertRange($cryptoMethod, $cryptoMethodMax); + + $sslVersion = $cryptoMethod === null + ? \CURL_SSLVERSION_DEFAULT + : self::curlMinSslVersion($easy, $cryptoMethod); + + if ($cryptoMethodMax !== null) { + $sslVersion |= self::curlMaxSslVersion($easy, $cryptoMethodMax); + } + + $conf[\CURLOPT_SSLVERSION] = $sslVersion; + } + + private static function curlMinSslVersion( + #[\SensitiveParameter] + EasyHandle $easy, + int $value + ): int { + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT) { + return \CURL_SSLVERSION_TLSv1_0; + } + + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT) { + return \CURL_SSLVERSION_TLSv1_1; + } + + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT) { + return \CURL_SSLVERSION_TLSv1_2; + } + + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) { + if (!CurlVersion::supportsTls13()) { + throw new RequestException( + 'Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL', + $easy->request + ); + } + + return \CURL_SSLVERSION_TLSv1_3; + } + + throw new InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + + private static function curlMaxSslVersion( + #[\SensitiveParameter] + EasyHandle $easy, + int $value + ): int { + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT) { + return self::requireCurlMaxSslVersion($easy, 'CURL_SSLVERSION_MAX_TLSv1_0'); + } + + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT) { + return self::requireCurlMaxSslVersion($easy, 'CURL_SSLVERSION_MAX_TLSv1_1'); + } + + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT) { + return self::requireCurlMaxSslVersion($easy, 'CURL_SSLVERSION_MAX_TLSv1_2'); + } + + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) { + return self::requireCurlMaxSslVersion($easy, 'CURL_SSLVERSION_MAX_TLSv1_3'); + } + + throw new InvalidArgumentException('Invalid crypto_method_max request option: unknown version provided'); + } + + private static function requireCurlMaxSslVersion( + #[\SensitiveParameter] + EasyHandle $easy, + string $constant + ): int { + if (\defined($constant)) { + /** @var int */ + return \constant($constant); + } + + throw new RequestException( + 'Invalid crypto_method_max request option: maximum TLS version control is not supported by your version of cURL', + $easy->request + ); + } + /** * This function ensures that a response was set on a transaction. If one * was not set, then the request is retried if possible. This error @@ -511,22 +2986,27 @@ class CurlFactory implements CurlFactoryInterface * error, causing the request to be sent through curl_multi_info_read() * without an error status. * - * @param callable(RequestInterface, array): PromiseInterface $handler + * @param callable(RequestInterface, array): PromiseInterface $handler + * + * @return PromiseInterface */ - private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface - { + private static function retryFailedRewind( + callable $handler, + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array $ctx + ): PromiseInterface { try { // Only rewind if the body has been read from. $body = $easy->request->getBody(); if ($body->tell() > 0) { $body->rewind(); } - } catch (\RuntimeException $e) { - $ctx['error'] = 'The connection unexpectedly failed without ' - . 'providing an error. The request would have been retried, ' - . 'but attempting to rewind the request body failed. ' - . 'Exception: ' . $e; - return self::createRejection($easy, $ctx); + } catch (\Exception $e) { + $ctx['error'] = 'The connection unexpectedly failed without providing an error. The request would have been retried, but attempting to rewind the request body failed.'; + + return self::createRejection($easy, $ctx, $e); } // Retry no more than 3 times before giving up. @@ -534,62 +3014,141 @@ class CurlFactory implements CurlFactoryInterface $easy->options['_curl_retries'] = 1; } elseif ($easy->options['_curl_retries'] == 2) { $ctx['error'] = 'The cURL request was retried 3 times ' - . 'and did not succeed. The most likely reason for the failure ' - . 'is that cURL was unable to rewind the body of the request ' - . 'and subsequent retries resulted in the same error. Turn on ' - . 'the debug option to see what went wrong. See ' - . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + .'and did not succeed. The most likely reason for the failure ' + .'is that cURL was unable to rewind the body of the request ' + .'and subsequent retries resulted in the same error. Turn on ' + .'the debug option to see what went wrong. See ' + .'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); } else { - $easy->options['_curl_retries']++; + ++$easy->options['_curl_retries']; } return $handler($easy->request, $easy->options); } - private function createHeaderFn(EasyHandle $easy): callable + /** + * Parses validated trailer field lines into an associative array keyed by + * lowercased field name, preserving first-occurrence key order and wire + * value order. + * + * @param list $lines + * + * @return array> + */ + private static function headersFromTrailerLines(array $lines): array { + $headers = []; + + foreach ($lines as $line) { + [$name, $value] = \explode(':', $line, 2); + $name = Psr7\Utils::asciiToLower(\trim($name, " \n\r\t\0\x0B")); + $headers[$name][] = \trim($value, " \n\r\t\0\x0B"); + } + + return $headers; + } + + private function createHeaderFn( + #[\SensitiveParameter] + EasyHandle $easy + ): callable { if (isset($easy->options['on_headers'])) { $onHeaders = $easy->options['on_headers']; if (!\is_callable($onHeaders)) { - throw new \InvalidArgumentException('on_headers must be callable'); + throw new InvalidArgumentException('on_headers must be callable'); } } else { $onHeaders = null; } - return static function ($ch, $h) use ( + $startingResponse = false; + $collectingTrailers = false; + $retainTrailers = isset($easy->options['on_trailers']); + + return static function ($ch, string $h) use ( $onHeaders, $easy, - &$startingResponse - ) { - $value = \trim($h); - if ($value === '') { + &$startingResponse, + &$collectingTrailers, + $retainTrailers + ): int { + $value = \trim($h, " \n\r\t\0\x0B"); + if ($h === "\r\n" || $h === "\n" || $h === "\r" || $h === '') { + if ($collectingTrailers) { + // A blank line ends the trailer section; the response has + // already been created. + return \strlen($h); + } + $startingResponse = true; + try { $easy->createResponse(); - } catch (\Exception $e) { + } catch (\Throwable $e) { + $easy->response = null; $easy->createResponseException = $e; + return -1; } - if ($onHeaders !== null) { + + if ($easy->responseHeaderException !== null) { + return -1; + } + + if ($onHeaders !== null && $easy->response !== null) { try { - $onHeaders($easy->response); - } catch (\Exception $e) { + $onHeaders($easy->response, $easy->request); + } catch (\Throwable $e) { // Associate the exception with the handle and trigger // a curl header write error by returning 0. $easy->onHeadersException = $e; + return -1; } } - } elseif ($startingResponse) { + } elseif ($startingResponse || $collectingTrailers) { + if ($easy->response !== null && !HeaderProcessor::isStatusLineCandidate($h)) { + // Trailer fields arrive through the header callback after + // the body; a new header block always begins with a status + // line. + $collectingTrailers = true; + + if ($retainTrailers && HeaderProcessor::isValidHeaderFieldLine($h)) { + $easy->trailers[] = $value; + } + } else { + $collectingTrailers = false; + $easy->trailers = []; + $easy->headers = [$value]; + } + $startingResponse = false; - $easy->headers = [$value]; } else { $easy->headers[] = $value; } + return \strlen($h); }; } + + public function __destruct() + { + try { + $this->doClose(false); + } catch (\Throwable $e) { + // Destructors must not throw. + } + } + + public function __unserialize(array $data): void + { + $this->closed = true; + $this->handles = []; + $this->shareHandle = null; + + throw new \LogicException(static::class.' should never be unserialized'); + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php index fe57ed5d5..ddafa3dc7 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -1,5 +1,7 @@ true, + 'transport_sharing' => true, + ]; + + private CurlFactoryInterface $factory; + + private bool $ownsFactory; + + private bool $closed = false; + + private ?CurlShareHandleState $shareHandleState; /** * Accepts an associative array of options: * * - handle_factory: Optional curl factory used to create cURL handles. + * - transport_sharing: Optional transport sharing mode. * - * @param array{handle_factory?: ?CurlFactoryInterface} $options Array of options to use with the handler + * @param array{handle_factory?: ?CurlFactoryInterface, transport_sharing?: mixed} $options Array of options to use with the handler */ public function __construct(array $options = []) { - $this->factory = $options['handle_factory'] - ?? new CurlFactory(3); - } - - public function __invoke(RequestInterface $request, array $options): PromiseInterface - { - if (isset($options['delay'])) { - \usleep($options['delay'] * 1000); + foreach ($options as $name => $_) { + if (!isset(self::KNOWN_CONSTRUCTOR_OPTIONS[$name])) { + throw new InvalidArgumentException(\sprintf('Invalid CurlHandler constructor option "%s".', DiagnosticValue::escape((string) $name))); + } } + CurlShareHandleState::assertNoRequiredSharingCustomFactoryConflict($options, 'CurlHandler'); + $transportSharing = $options['transport_sharing'] ?? null; + $sharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing'); + + if (\array_key_exists('handle_factory', $options) && $options['handle_factory'] !== null) { + $this->shareHandleState = null; + $this->factory = $options['handle_factory']; + $this->ownsFactory = false; + + return; + } + + $this->shareHandleState = $sharingMode !== TransportSharing::NONE + ? CurlShareHandleState::fromOption($transportSharing) + : null; + + $this->factory = $this->shareHandleState !== null + ? new CurlFactory(3, $this->shareHandleState->mode, $this->shareHandleState) + : new CurlFactory(3); + + $this->ownsFactory = true; + } + + /** + * @return PromiseInterface + */ + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { + $this->assertOpen(); + HostValidator::assertRequestHost($request); + + if (isset($options['delay'])) { + Clock::sleep((int) ($options['delay'] * 1000)); + } + + // A Multiplexing::NONE request option holds unconditionally here: + // the transfer runs alone during the blocking curl_exec(), and even + // under persistent transport sharing an in-use connection cannot be + // joined from another multi handle, so it never shares its + // connection with a concurrent transfer. $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); $easy->errno = \curl_errno($easy->handle); return CurlFactory::finish($this, $easy, $this->factory); } + + /** + * Closes native cURL resources owned by this handler. + * + * After closing, the handler is terminal and must not be reused. + */ + public function close(): void + { + $this->doClose(true); + } + + public function __destruct() + { + try { + $this->doClose(false); + } catch (\Throwable $e) { + // Destructors must not throw. + } + } + + public function __unserialize(array $data): void + { + $this->closed = true; + + throw new \LogicException(static::class.' should never be unserialized'); + } + + private function assertOpen(): void + { + if ($this->closed) { + // Programmer misuse (reusing a closed handler), not a transfer failure; + // intentionally a LogicException outside the GuzzleException hierarchy. + throw new \BadMethodCallException('Cannot use the cURL handler after it has been closed.'); + } + } + + private function doClose(bool $explicit): void + { + if ($this->closed) { + return; + } + + $this->closed = true; + + try { + if ($this->ownsFactory && $this->factory instanceof CurlFactory) { + $this->factory->close(); + } + } catch (\Throwable $e) { + if ($explicit) { + throw $e; + } + } finally { + $this->shareHandleState = null; + } + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php index 4356d0247..79612ff1e 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -1,12 +1,23 @@ true, + 'max_host_connections' => true, + 'max_total_connections' => true, + 'multiplex' => true, + 'options' => true, + 'select_timeout' => true, + 'transport_sharing' => true, + ]; + + private const CONNECTION_CAP_OPTIONS = [ + 'max_host_connections' => 'CURLMOPT_MAX_HOST_CONNECTIONS', + 'max_total_connections' => 'CURLMOPT_MAX_TOTAL_CONNECTIONS', + ]; /** - * @var int + * cURL options that isolate a transfer from foreign proxy tunnel + * connections. Failing to apply either one would fall open into + * credential-bearing connection reuse. */ - private $selectTimeout; + private const PROXY_TUNNEL_ISOLATION_OPTIONS = [ + 'CURLOPT_FRESH_CONNECT', + 'CURLOPT_FORBID_REUSE', + ]; + + private CurlFactoryInterface $factory; + + private bool $ownsFactory; + + private ?CurlShareHandleState $shareHandleState; + + private float $selectTimeout; /** * @var int Will be higher than 0 when `curl_multi_exec` is still running. */ - private $active = 0; + private int $active = 0; /** * @var array Request entry handles, indexed by handle id in `addRequest`. * * @see CurlMultiHandler::addRequest */ - private $handles = []; + private array $handles = []; /** * @var array An array of delay times, indexed by handle id in `addRequest`. * * @see CurlMultiHandler::addRequest */ - private $delays = []; + private array $delays = []; /** * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() */ - private $options = []; + private array $options = []; + + /** + * @var bool Whether the "multiplex" constructor option disabled + * multiplexing on this handler's multi handle + */ + private bool $multiplexDisabled = false; + + /** + * @var resource|\CurlMultiHandle|null + */ + private $multiHandle; + + private bool $closed = false; + + private bool $closing = false; + + /** + * @var int Depth of nested guarded native operations (execution and + * handle removal, both of which can run user callbacks). A + * callback can re-enter tick(), and the nested frame must not + * clear the outer frame's guard; deferred work stays parked + * until the outermost frame unwinds. + */ + private int $multiExecDepth = 0; + + /** + * @var bool Guards finishDeferredWork() against re-entry from the + * guarded native removals it performs while flushing. + */ + private bool $finishingDeferredWork = false; + + /** + * @var array + */ + private array $deferredCancels = []; + + /** + * @var array Wait tokens of requests created from inside + * a cURL callback, keyed by handle id; native + * attachment is deferred until the outermost + * native execution unwinds. + */ + private array $deferredAdds = []; + + private bool $deferredClose = false; + + private bool $deferredCloseExplicit = false; + + /** + * @var string|null Owner signature of the proxy tunnels the multi handle's + * connection cache may hold + */ + private ?string $proxyTunnelOwner = null; + + /** @var array Count of attached transfers per proxy tunnel signature. */ + private array $activeProxyTunnelSignatures = []; + + /** @var array Maps an attached handle id to its proxy tunnel signature. */ + private array $activeProxyTunnelHandles = []; + + /** + * @var int Depth of nested processMessages() calls. Guards against + * multi-handle recreation re-entrancy from processMessages (a + * retried transfer re-invokes the handler) and keeps deferred + * work parked until the outermost message loop unwinds. + */ + private int $messageProcessingDepth = 0; /** * This handler accepts the following options: * * - handle_factory: An optional factory used to create curl handles + * - transport_sharing: Optional transport sharing mode. * - select_timeout: Optional timeout (in seconds) to block before timing * out while selecting curl handles. Defaults to 1 second. + * - max_host_connections: Optional maximum concurrent connections per host. + * - max_total_connections: Optional maximum concurrent connections overall. + * - multiplex: Optional Multiplexing::NONE to disallow multiplexing on + * this handler's multi handle. The eager, wait, and required modes are + * request options, not handler options; Multiplexing::NONE is also + * conditionally accepted as a request option value. * - options: An associative array of CURLMOPT_* options and * corresponding values for curl_multi_setopt() */ public function __construct(array $options = []) { - $this->factory = $options['handle_factory'] ?? new CurlFactory(50); + foreach ($options as $name => $_) { + if (!isset(self::KNOWN_CONSTRUCTOR_OPTIONS[$name])) { + throw new InvalidArgumentException(\sprintf('Invalid CurlMultiHandler constructor option "%s".', Psr7\DiagnosticValue::escape((string) $name))); + } + } - if (isset($options['select_timeout'])) { - $this->selectTimeout = $options['select_timeout']; - } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { - @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED); - $this->selectTimeout = (int) $selectTimeout; + $handlerMultiplex = $options['multiplex'] ?? null; + if (null !== $handlerMultiplex && Multiplexing::NONE !== $handlerMultiplex) { + if (\in_array($handlerMultiplex, [Multiplexing::EAGER, Multiplexing::WAIT, Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new InvalidArgumentException('The "multiplex" CurlMultiHandler option only accepts Multiplexing::NONE; the eager, wait, and required modes are request options.'); + } + + throw new InvalidArgumentException(\sprintf('The "multiplex" CurlMultiHandler option must be null or Multiplexing::NONE; received %s.', \get_debug_type($handlerMultiplex))); + } + $this->multiplexDisabled = null !== $handlerMultiplex; + + if ($this->multiplexDisabled && !\defined('CURLMOPT_PIPELINING')) { + // ext-curl only defines the constant when built against libcurl + // 7.16 or newer headers, and such builds compile out the matching + // curl_multi_setopt() case, so the guarantee cannot be applied. + throw new InvalidArgumentException('The "multiplex" CurlMultiHandler option requires CURLMOPT_PIPELINING, but it is not available in the installed PHP cURL extension.'); + } + + CurlShareHandleState::assertNoRequiredSharingCustomFactoryConflict($options, 'CurlMultiHandler'); + $transportSharing = $options['transport_sharing'] ?? null; + $sharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing'); + + $selectTimeout = $options['select_timeout'] ?? 1.0; + Timeout::toMilliseconds($selectTimeout, 'select_timeout'); + $this->selectTimeout = (float) $selectTimeout; + + $multiOptions = $options['options'] ?? []; + if (!\is_array($multiOptions)) { + throw new InvalidArgumentException('options must be an array of cURL multi options'); + } + + $this->options = $multiOptions; + self::rejectConflictingCurlMultiOptions($this->options); + $this->addConnectionCapOptions($options); + + if ($this->multiplexDisabled) { + // CURLPIPE_NOTHING; the constant itself needs libcurl 7.43 + // headers, newer than the oldest supported runtimes. + $this->options[\CURLMOPT_PIPELINING] = 0; + } + + $connectionCapOption = self::firstConnectionCapOption($options); + if ($connectionCapOption !== null && !CurlVersion::supportsSharedPoolConnectionCaps()) { + $persistentShareState = $transportSharing instanceof CurlShareHandleState + && \in_array($sharingMode, [TransportSharing::PERSISTENT_PREFER, TransportSharing::PERSISTENT_REQUIRE], true); + + if ($persistentShareState || $sharingMode === TransportSharing::PERSISTENT_REQUIRE) { + throw new InvalidArgumentException(\sprintf('%s cannot be combined with persistent transport sharing because applying connection caps to shared connection pools requires libcurl %s or higher.', $connectionCapOption, CurlVersion::SHARED_POOL_CONNECTION_CAP_VERSION)); + } + + if ($sharingMode === TransportSharing::PERSISTENT_PREFER) { + // libcurl below 8.22.0 does not apply cURL multi connection + // caps to transfers using a shared connection pool (curl + // #22265), so the best honorable offer for preferred persistent + // sharing is a handler-lifetime share. + $transportSharing = TransportSharing::HANDLER_PREFER; + $sharingMode = TransportSharing::HANDLER_PREFER; + } + } + + if (\array_key_exists('handle_factory', $options) && $options['handle_factory'] !== null) { + $this->shareHandleState = null; + $this->factory = $options['handle_factory']; + $this->ownsFactory = false; } else { - $this->selectTimeout = 1; + $this->shareHandleState = $sharingMode !== TransportSharing::NONE + ? CurlShareHandleState::fromOption($transportSharing) + : null; + + $this->factory = $this->shareHandleState !== null + ? new CurlFactory(50, $this->shareHandleState->mode, $this->shareHandleState) + : new CurlFactory(50); + + $this->ownsFactory = true; } - - $this->options = $options['options'] ?? []; - } - - /** - * @param string $name - * - * @return resource|\CurlMultiHandle - * - * @throws \BadMethodCallException when another field as `_mh` will be gotten - * @throws \RuntimeException when curl can not initialize a multi handle - */ - public function __get($name) - { - if ($name !== '_mh') { - throw new \BadMethodCallException("Can not get other property as '_mh'."); - } - - $multiHandle = \curl_multi_init(); - - if (false === $multiHandle) { - throw new \RuntimeException('Can not initialize curl multi handle.'); - } - - $this->_mh = $multiHandle; - - foreach ($this->options as $option => $value) { - // A warning is raised in case of a wrong option. - curl_multi_setopt($this->_mh, $option, $value); - } - - return $this->_mh; } public function __destruct() { - if (isset($this->_mh)) { - \curl_multi_close($this->_mh); - unset($this->_mh); + try { + $this->doClose(false); + } catch (\Throwable $e) { + // Destructors must not throw. } } - public function __invoke(RequestInterface $request, array $options): PromiseInterface + public function __unserialize(array $data): void { + $this->closed = true; + + throw new \LogicException(static::class.' should never be unserialized'); + } + + /** + * @return PromiseInterface + */ + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { + $this->assertOpen(); + HostValidator::assertRequestHost($request); + $easy = $this->factory->create($request, $options); + + try { + $this->rejectDisabledMultiplexConflict($easy, $options); + $this->applyMultiplexNone($easy, $options); + $this->applyProxyTunnelOwnership($easy); + } catch (\Throwable $e) { + try { + $this->factory->release($easy); + } catch (\Throwable $releaseFailure) { + // Preserve the original failure. + } + + throw $e; + } + $id = (int) $easy->handle; + $waitToken = new \stdClass(); + + $promise = null; + /** @var Promise $promise */ $promise = new Promise( - [$this, 'execute'], - function () use ($id) { - return $this->cancel($id); + function () use ($id, $waitToken, $easy, &$promise): void { + // Waiting cannot drive native cURL while a callback has the + // multi handle busy; fail the wait promptly instead of + // self-deadlocking. + $stalled = $this->multiExecDepth > 0 + ? $this->failNestedWait($id, $waitToken) + : $this->executeUntil($id, $waitToken); + + // Settling can be queued, and guzzlehttp/promises drains the + // queue before deciding a wait function achieved nothing. + P\Utils::queue()->run(); + + // Never null: assigned below before any wait can invoke this. + /** @var Promise $promise */ + if ($easy->deferredSettled || !P\State::isPending($promise)) { + return; + } + + // Neither path guarantees the transfer settled, and returning + // while pending makes guzzlehttp/promises reject with a bare + // string naming nothing. + $message = \sprintf('Waiting on cURL multi handler transfer %d cannot make progress (%s).', $id, $stalled); + + // The entry is gone, so the easy handle is the only route to + // a response that already arrived. + $response = $easy->response; + $failure = $response !== null + ? new ResponseException($message, $easy->request, $response) + : new RequestException($message, $easy->request); + + $promise->reject($failure); + }, + function () use ($id, $waitToken): void { + $this->cancel($id, $waitToken); } ); - $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + $entry = ['easy' => $easy, 'deferred' => $promise, 'wait_token' => $waitToken]; + try { + $this->addRequest($entry); + } catch (\Throwable $e) { + throw $this->discardPendingRequest($id, $entry, $e); + } return $promise; } + /** + * The "multiplex" request option sets CURLOPT_PIPEWAIT, which libcurl + * ignores entirely when the multi handle disallows multiplexing, so an + * explicit request for multiplexing on a handler configured with + * Multiplexing::NONE is a configuration error. The required family + * conflicts marker-independently: a required guarantee on such a handler + * is contradictory even when the transfer would not wait. The default + * WAIT mode adapts instead of conflicting: the handler option wins and + * nothing waits. + */ + private function rejectDisabledMultiplexConflict( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array $options + ): void { + if (!$this->multiplexDisabled) { + return; + } + + $multiplex = $options['multiplex'] ?? null; + + if (Multiplexing::WAIT === $multiplex && !$easy->usesPipewait) { + // Explicit wait only conflicts when the transfer would actually + // wait; an HTTP/1.1 wait request never sets the marker. + return; + } + + if (!\in_array($multiplex, [Multiplexing::WAIT, Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + return; + } + + throw new InvalidArgumentException('The "multiplex" request option cannot be combined with a CurlMultiHandler whose "multiplex" option is Multiplexing::NONE; remove the handler option or set the request option to "eager".'); + } + + /** + * A Multiplexing::NONE request option is a sole-use guarantee: the + * transfer must not share its connection with any concurrent transfer. + * It holds structurally on a handler whose "multiplex" option is + * Multiplexing::NONE, and for HTTP/1.x transfers, which never join a + * multiplexed connection and open connections nothing can join. An + * HTTP/2 or HTTP/3 request on a handler that multiplexes is rejected, + * as is any configuration under which the guarantee cannot be verified + * (custom handle factories control the native handle), cannot be + * hardened (challenge-response authentication retries and Expect + * 417 retries re-enter connection selection as internal follows, which + * disarm CURLOPT_FRESH_CONNECT), or must not be hardened (required + * persistent sharing forbids fresh connections). On runtimes whose + * matcher can hand an HTTP/1.x transfer an idle multiplexed connection + * (below libcurl 7.77.0, and 8.11.0-8.12.1), accepted transfers force a + * fresh connection. + */ + private function applyMultiplexNone( + #[\SensitiveParameter] + EasyHandle $easy, + #[\SensitiveParameter] + array $options + ): void { + if (Multiplexing::NONE !== ($options['multiplex'] ?? null) || $this->multiplexDisabled) { + return; + } + + if (!$this->ownsFactory) { + throw new InvalidArgumentException('The "multiplex" request option can only be Multiplexing::NONE on a CurlMultiHandler with a custom "handle_factory" when the handler\'s own "multiplex" option is Multiplexing::NONE, because the guarantee is enforced against the native easy handle the factory controls.'); + } + + $version = $easy->request->getProtocolVersion(); + if ('1.0' !== $version && '1.1' !== $version) { + throw new InvalidArgumentException('The "multiplex" request option can only be Multiplexing::NONE for an HTTP/1.x request on a CurlMultiHandler that permits multiplexing; set the "multiplex" client or CurlMultiHandler constructor option to Multiplexing::NONE to disable multiplexing for every transfer, or send the request with its "version" option set to "1.1".'); + } + + if (null !== $this->shareHandleState && TransportSharing::PERSISTENT_REQUIRE === $this->shareHandleState->mode) { + // The matcher-window hardening below may force a fresh + // connection, which required persistent sharing forbids because + // it disables connection reuse (CurlFactory applies the same + // rule to the raw option); rejected on every runtime so + // acceptance stays version-independent. + throw new InvalidArgumentException('The "multiplex" request option cannot be Multiplexing::NONE on a CurlMultiHandler that permits multiplexing and requires persistent transport sharing; set the "multiplex" client or CurlMultiHandler constructor option to Multiplexing::NONE, or use TransportSharing::PERSISTENT_PREFER.'); + } + + if (\defined('CURLOPT_HTTPAUTH') + && isset($options['curl']) + && \is_array($options['curl']) + && \array_key_exists((int) \constant('CURLOPT_HTTPAUTH'), $options['curl']) + ) { + // Key presence alone conflicts: challenge-response retries are + // libcurl-internal follows, which disarm CURLOPT_FRESH_CONNECT + // (reuse_fresh && !this_is_a_follow), so the hardening below + // cannot cover them. + throw new InvalidArgumentException('The "multiplex" request option cannot be Multiplexing::NONE combined with the raw CURLOPT_HTTPAUTH cURL option on a CurlMultiHandler that permits multiplexing; remove the raw option, or set the "multiplex" client or CurlMultiHandler constructor option to Multiplexing::NONE.'); + } + + if (Psr7\Utils::caselessContains($easy->request->getHeaderLine('Expect'), '100-continue')) { + // libcurl arms its Expect handling by a caseless substring scan + // of the header value (Curl_compareheader), so any value + // containing 100-continue can make a 417 response retry as an + // internal follow, which disarms CURLOPT_FRESH_CONNECT; requests + // without the header are safe because the factory suppresses + // libcurl's automatic Expect. + throw new InvalidArgumentException('The "multiplex" request option cannot be Multiplexing::NONE for a request carrying an "Expect: 100-continue" header on a CurlMultiHandler that permits multiplexing; remove the explicitly supplied "Expect" header, set the "expect" request option to false to prevent it being added automatically, or set the "multiplex" client or CurlMultiHandler constructor option to Multiplexing::NONE.'); + } + + if (CurlVersion::supportsHttpVersionReuseMatching()) { + return; + } + + // Unqualified curl_setopt so the test bootstrap shadow records it. + if (true !== curl_setopt($easy->handle, \CURLOPT_FRESH_CONNECT, true)) { + // The hardening is the guarantee on these runtimes; failing to + // apply it must fail closed, mirroring applyCurlOptions(). + throw new InvalidArgumentException('Unable to set cURL option CURLOPT_FRESH_CONNECT.'); + } + } + + /** + * @param array $options + */ + private static function rejectConflictingCurlMultiOptions(array $options): void + { + if ($options === []) { + return; + } + + $conflictingOptions = self::conflictingCurlMultiOptions(); + foreach ($options as $option => $_) { + if (\array_key_exists($option, $conflictingOptions)) { + throw new InvalidArgumentException(\sprintf('Passing %s in the cURL multi handler "options" is not supported. Use %s instead.', self::formatCurlMultiOption($option), $conflictingOptions[$option])); + } + } + } + + /** + * @param array $options + */ + private static function firstConnectionCapOption(array $options): ?string + { + foreach (self::CONNECTION_CAP_OPTIONS as $name => $_) { + if (($options[$name] ?? null) !== null) { + return $name; + } + } + + return null; + } + + /** + * @param array $options + */ + private function addConnectionCapOptions(array $options): void + { + foreach (self::CONNECTION_CAP_OPTIONS as $name => $constant) { + $value = $options[$name] ?? null; + if ($value === null) { + continue; + } + + if (!\is_int($value) || $value < 1) { + throw new InvalidArgumentException(\sprintf('%s must be a positive integer.', $name)); + } + + if (!\defined($constant)) { + throw new InvalidArgumentException(\sprintf('%s requires %s, but it is not available in the installed PHP cURL extension.', $name, $constant)); + } + + $option = \constant($constant); + if (\array_key_exists($option, $this->options)) { + throw new InvalidArgumentException(\sprintf('%s conflicts with a %s entry in the "options" array.', $name, $constant)); + } + + $this->options[$option] = $value; + } + } + + /** + * @param int|string $option + */ + private static function formatCurlMultiOption($option): string + { + if (!\is_int($option)) { + return \sprintf('"%s"', Psr7\DiagnosticValue::escape((string) $option)); + } + + static $names = null; + + if (null === $names) { + $names = []; + foreach (\get_defined_constants(true)['curl'] ?? [] as $name => $value) { + if (\is_int($value) && \strpos($name, 'CURLMOPT_') === 0 && !isset($names[$value])) { + $names[$value] = $name; + } + } + } + + if (isset($names[$option])) { + return \sprintf('%s (%d)', $names[$option], $option); + } + + return (string) $option; + } + + /** + * @return array + */ + private static function conflictingCurlMultiOptions(): array + { + static $options = null; + + if ($options !== null) { + return $options; + } + + $options = []; + + self::addConflictingCurlMultiOption($options, 'CURLMOPT_MAX_HOST_CONNECTIONS', 'the "max_host_connections" client option or cURL multi handler option'); + self::addConflictingCurlMultiOption($options, 'CURLMOPT_MAX_TOTAL_CONNECTIONS', 'the "max_total_connections" client option or cURL multi handler option'); + self::addConflictingCurlMultiOption($options, 'CURLMOPT_PIPELINING', 'Multiplexing::NONE via the "multiplex" cURL multi handler or client option to disable multiplexing, or remove the raw option for the runtime default (multiplexing defaults on from libcurl 7.62, except 7.65.0 and 7.65.1)'); + + return $options; + } + + /** + * @param array $options + */ + private static function addConflictingCurlMultiOption(array &$options, string $constant, string $replacement): void + { + if (!\defined($constant)) { + return; + } + + $value = \constant($constant); + if (\is_int($value)) { + $options[$value] = $replacement; + } + } + + /** + * Isolates the connection cache when the request's proxy tunnel section + * differs from the one the multi handle's cache may already hold. + */ + private function applyProxyTunnelOwnership( + #[\SensitiveParameter] + EasyHandle $easy + ): void { + $signature = $easy->proxyTunnelSignature; + if ($signature === null || $signature === $this->proxyTunnelOwner) { + return; + } + + if ($this->proxyTunnelOwner === null) { + // No in-domain transfer has ever run on this multi handle: latch + // the owner without destroying pooled direct connections. + $this->proxyTunnelOwner = $signature; + + return; + } + + if ( + $this->handles === [] + && 0 === $this->multiExecDepth + && 0 === $this->messageProcessingDepth + && $this->deferredCancels === [] + && !$this->deferredClose + ) { + // Idle: hand the connection cache over by recreating the multi + // handle. getMultiHandle() lazily re-initializes it (re-applying + // the CURLMOPT_* options) on the next access. + if ($this->multiHandle !== null) { + \curl_multi_close($this->multiHandle); + $this->multiHandle = null; + } + $this->proxyTunnelOwner = $signature; + + return; + } + + // Busy: isolate this transfer from the owner's pooled tunnels. + $this->isolateProxyTunnelTransfer($easy); + } + + private function addHandleToMulti( + int $id, + #[\SensitiveParameter] + EasyHandle $easy + ): void { + $this->isolateFromForeignActiveProxyTunnel($easy); + + $multiHandle = $this->getMultiHandle(); + + // Unqualified curl_multi_add_handle so the test bootstrap shadow can + // override the result. + $result = curl_multi_add_handle($multiHandle, $easy->handle); + + if (\CURLM_OK !== $result) { + if (\PHP_VERSION_ID < 80226 || (\PHP_VERSION_ID >= 80300 && \PHP_VERSION_ID < 80314)) { + // Before PHP 8.2.26 and 8.3.14, ext-curl kept the easy handle + // in its multi bookkeeping even when the native add failed + // (https://github.com/php/php-src/pull/16302); remove it so + // the handle can be disposed safely. + \curl_multi_remove_handle($multiHandle, $easy->handle); + } + + throw new RequestException(\sprintf('Unable to add the cURL handle to the cURL multi handler: %s (%d).', (string) \curl_multi_strerror($result), $result), $easy->request); + } + + $this->markProxyTunnelActive($id, $easy); + + if (isset($this->handles[$id])) { + $this->handles[$id]['attached'] = true; + } + } + + private function isolateFromForeignActiveProxyTunnel( + #[\SensitiveParameter] + EasyHandle $easy + ): void { + $signature = $easy->proxyTunnelSignature; + + if ($signature === null || $this->activeProxyTunnelSignatures === []) { + return; + } + + if (\count($this->activeProxyTunnelSignatures) === 1 && isset($this->activeProxyTunnelSignatures[$signature])) { + return; + } + + $this->isolateProxyTunnelTransfer($easy); + } + + private function isolateProxyTunnelTransfer( + #[\SensitiveParameter] + EasyHandle $easy + ): void { + foreach (self::PROXY_TUNNEL_ISOLATION_OPTIONS as $name) { + try { + // Unqualified curl_setopt so the test bootstrap shadow records it. + $applied = curl_setopt($easy->handle, (int) \constant($name), true); + } catch (\Throwable $e) { + throw new RequestException(self::proxyTunnelIsolationFailureMessage($name), $easy->request, 0, $e); + } + + if (true !== $applied) { + throw new RequestException(self::proxyTunnelIsolationFailureMessage($name), $easy->request); + } + } + } + + private static function proxyTunnelIsolationFailureMessage(string $name): string + { + return \sprintf('Unable to apply the %s cURL option required to isolate the transfer from foreign proxy tunnel connections.', $name); + } + + private function markProxyTunnelActive(int $id, EasyHandle $easy): void + { + $signature = $easy->proxyTunnelSignature; + if ($signature === null) { + return; + } + + if (isset($this->activeProxyTunnelHandles[$id])) { + if ($this->activeProxyTunnelHandles[$id] === $signature) { + return; + } + + $this->unmarkProxyTunnelActiveById($id); + } + + $this->activeProxyTunnelHandles[$id] = $signature; + $this->activeProxyTunnelSignatures[$signature] = ($this->activeProxyTunnelSignatures[$signature] ?? 0) + 1; + } + + private function unmarkProxyTunnelActiveById(int $id): void + { + if (!isset($this->activeProxyTunnelHandles[$id])) { + return; + } + + $signature = $this->activeProxyTunnelHandles[$id]; + unset($this->activeProxyTunnelHandles[$id]); + + if (!isset($this->activeProxyTunnelSignatures[$signature])) { + return; + } + + --$this->activeProxyTunnelSignatures[$signature]; + + if ($this->activeProxyTunnelSignatures[$signature] <= 0) { + unset($this->activeProxyTunnelSignatures[$signature]); + } + } + /** * Ticks the curl event loop. */ public function tick(): void { - // Add any delayed handles if needed. - if ($this->delays) { - $currentTime = Utils::currentTime(); + $this->tickFor(null, null); + } + + /** + * Ticks the curl event loop, returning before the blocking select if the + * targeted transfer has settled, been canceled, or been replaced by a + * request that reused its native handle ID. + */ + private function tickFor(?int $targetId, ?object $waitToken): void + { + $this->assertOpen(); + + // Add any delayed handles if needed. Attachment is skipped while a + // callback has native execution busy; the outer frame attaches due + // transfers once it unwinds. + if ($this->delays && 0 === $this->multiExecDepth) { + $currentTime = Clock::now(); foreach ($this->delays as $id => $delay) { if ($currentTime >= $delay) { + $entry = $this->handles[$id]; unset($this->delays[$id]); - \curl_multi_add_handle( - $this->_mh, - $this->handles[$id]['easy']->handle - ); + + try { + $this->addHandleToMulti($id, $entry['easy']); + } catch (\Throwable $e) { + $rejection = $this->discardPendingRequest($id, $entry, $e); + if (P\State::isPending($entry['deferred'])) { + $entry['deferred']->reject($rejection); + } + } } } } - // Step through the task queue which may add additional requests. - P\Utils::queue()->run(); + // Run curl_multi_exec in the queue to enable other async tasks to + // run, surface completions, and drain any work they queued so a + // ready cancellation or new transfer is not held behind the select. + do { + P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue'])); - if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) { + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + + if ($this->multiExecDepth > 0) { + // A cURL callback re-entered the handler while native + // execution is running; the outer frame drives native cURL + // once it unwinds. + return; + } + + if ($this->closed || $this->closing || !$this->hasMultiHandle()) { + return; + } + + $this->processMessages(); + + if ($this->closed || $this->closing || !$this->hasMultiHandle()) { + return; + } + } while (!P\Utils::queue()->isEmpty()); + + if ($targetId !== null && !$this->hasRequest($targetId, $waitToken)) { + return; + } + + if ($this->active && \curl_multi_select($this->getMultiHandle(), $this->effectiveSelectTimeout()) === -1) { // Perform a usleep if a select returns -1. // See: https://bugs.php.net/bug.php?id=61141 \usleep(250); } - while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM); + do { + $exec = $this->executeMulti(); + + if ($this->closed || $this->closing || !$this->hasMultiHandle()) { + return; + } + + // Prevent busy looping for slow HTTP requests. + if ($exec === \CURLM_CALL_MULTI_PERFORM) { + \curl_multi_select($this->getMultiHandle(), $this->effectiveSelectTimeout()); + } + } while ($exec === \CURLM_CALL_MULTI_PERFORM); $this->processMessages(); } + /** + * Runs \curl_multi_exec() inside the event loop, to prevent busy looping + */ + private function tickInQueue(): void + { + if ($this->multiExecDepth > 0) { + // A cURL callback re-entered the handler while native execution + // is running; the outer frame drives native cURL once it unwinds. + return; + } + + if ($this->closed || $this->closing || !$this->hasMultiHandle()) { + return; + } + + $exec = $this->executeMulti(); + + if ($this->closed || $this->closing || !$this->hasMultiHandle()) { + return; + } + + if ($exec === \CURLM_CALL_MULTI_PERFORM) { + \curl_multi_select($this->getMultiHandle(), 0); + P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue'])); + } + } + /** * Runs until all outstanding connections have completed. */ public function execute(): void { + $this->assertOpen(); + + if ($this->multiExecDepth > 0) { + // Native cURL cannot be driven while a callback has it busy, so + // the loop would spin without ever progressing. + throw new \LogicException('Cannot run the cURL multi event loop from inside a cURL callback; the callback must return before transfers can progress.'); + } + $queue = P\Utils::queue(); - while ($this->handles || !$queue->isEmpty()) { - // If there are no transfers, then sleep for the next delay - if (!$this->active && $this->delays) { - \usleep($this->timeToNext()); + while (!$this->closed && !$this->closing && ($this->handles || !$queue->isEmpty())) { + // If there are no transfers, then sleep for the next delay, + // unless ready queue work could change what is pending. + if (!$this->active && $this->delays && $queue->isEmpty()) { + Clock::sleep($this->timeToNext()); } $this->tick(); } } - private function addRequest(array $entry): void + /** + * Runs the event loop until the given transfer has finished, so waiting + * on a promise does not wait for every other transfer on the handler + * like execute() does. + * + * The native cURL handle ID can be reused by a request created from a + * completion callback, so the wait token guards against waiting on an + * unrelated transfer that inherited the ID. + * + * @return string Why waiting stopped, for the caller to report when the + * transfer did not settle + */ + private function executeUntil(int $id, object $waitToken): string { + $this->assertOpen(); + + $queue = P\Utils::queue(); + + while ( + !$this->closed + && !$this->closing + && $this->hasRequest($id, $waitToken) + ) { + // If the transfer is delayed, then sleep until it is due, unless + // ready queue work could cancel or replace it first. + if (!$this->active && isset($this->delays[$id]) && $queue->isEmpty()) { + Clock::sleep($this->timeToNext()); + } + $this->tickFor($id, $waitToken); + } + + // Sample before the drain below, which can add or remove an entry + // under this ID and so rewrite the answer. + $stalled = $this->describeStalledWait($id, $waitToken); + + if (!$this->closed && !$this->closing && !$queue->isEmpty()) { + $queue->run(); + } + + return $stalled; + } + + /** + * Describes the state that stopped a wait, for the fallback rejection in + * __invoke(). Only meaningful when the transfer did not settle. + */ + private function describeStalledWait(int $id, object $waitToken): string + { + if ($this->hasRequest($id, $waitToken)) { + // Reached when waiting stopped for another reason, such as the + // handler being closed underneath it. + return 'the handler stopped driving it while it was still tracked'; + } + + return isset($this->handles[$id]) + ? 'its native cURL handle ID was reused by another request' + : 'its entry was removed without settling'; + } + + /** + * Checks that the request with the given handle ID is still pending and, + * when a wait token is given, has not been replaced by a request that + * reused the ID. + */ + private function hasRequest(int $id, ?object $waitToken = null): bool + { + if (!isset($this->handles[$id])) { + return false; + } + + return $waitToken === null || ($this->handles[$id]['wait_token'] ?? null) === $waitToken; + } + + /** + * Closes native cURL resources owned by this handler. + * + * Pending transfers are rejected with HandlerClosedException. After + * closing, the handler is terminal and must not be reused. + */ + public function close(): void + { + $this->doClose(true); + } + + private function assertOpen(): void + { + if ($this->closed || $this->closing) { + // Programmer misuse (reusing a closed handler), not a transfer failure; + // intentionally a LogicException outside the GuzzleException hierarchy. + throw new \BadMethodCallException('Cannot use the cURL multi handler after it has been closed.'); + } + } + + private function doClose(bool $explicit): void + { + if ($this->closed || $this->closing) { + return; + } + + $this->closing = true; + $failure = null; + + if ($this->multiExecDepth > 0 || $this->messageProcessingDepth > 0) { + $this->deferClose($explicit, $failure); + + if ($explicit && $failure !== null) { + throw $failure; + } + + return; + } + + try { + $this->cleanupPendingTransfers($explicit, $failure); + $this->closeMultiHandle($failure); + $this->closeOwnedFactory($failure); + } finally { + $this->finishClose(); + } + + if ($explicit && $failure !== null) { + throw $failure; + } + } + + private function deferClose(bool $explicit, ?\Throwable &$failure): void + { + $this->deferredClose = true; + $this->deferredCloseExplicit = $this->deferredCloseExplicit || $explicit; + + $entries = $this->handles; + + $this->handles = []; + $this->delays = []; + $this->deferredAdds = []; + + foreach ($entries as $id => $entry) { + $this->deferredCancels[$id] = [ + 'easy' => $entry['easy'], + 'attached' => !empty($entry['attached']), + ]; + + if ($explicit && P\State::isPending($entry['deferred'])) { + $this->captureFailure($failure, function () use ($entry): void { + $entry['deferred']->reject(new HandlerClosedException('The cURL multi handler was closed before the transfer completed.', $entry['easy']->request)); + }); + } + } + } + + private function finishClose(): void + { + $this->handles = []; + $this->delays = []; + $this->deferredCancels = []; + $this->deferredAdds = []; + $this->activeProxyTunnelSignatures = []; + $this->activeProxyTunnelHandles = []; + $this->active = 0; + $this->shareHandleState = null; + $this->deferredClose = false; + $this->deferredCloseExplicit = false; + $this->closed = true; + $this->closing = false; + } + + private function captureFailure(?\Throwable &$failure, callable $callback): void + { + try { + $callback(); + } catch (\Throwable $e) { + if ($failure === null) { + $failure = $e; + } + } + } + + /** + * @param array{easy: EasyHandle, deferred: Promise, wait_token: object} $entry + */ + private function discardPendingRequest(int $id, array $entry, \Throwable $failure): \Throwable + { + unset($this->handles[$id], $this->delays[$id], $this->deferredAdds[$id]); + + try { + $this->disposeEasyHandle($entry['easy']); + } catch (\Throwable $e) { + // Preserve the original attach failure. + } + + return $failure; + } + + private function cleanupPendingTransfers(bool $reject, ?\Throwable &$failure): void + { + $entries = $this->handles; + + $this->handles = []; + $this->delays = []; + $this->deferredAdds = []; + + foreach ($entries as $id => $entry) { + $easy = $entry['easy']; + $attached = !empty($entry['attached']); + + if ($attached && $this->hasMultiHandle() && self::hasEasyHandle($easy)) { + $this->captureFailure($failure, function () use ($id, $easy): void { + $this->removeHandleFromMulti($id, $easy->handle); + }); + } + + if ($reject && P\State::isPending($entry['deferred'])) { + $this->captureFailure($failure, function () use ($entry): void { + $entry['deferred']->reject(new HandlerClosedException('The cURL multi handler was closed before the transfer completed.', $entry['easy']->request)); + }); + } + + $this->captureFailure($failure, function () use ($easy): void { + $this->disposeEasyHandle($easy); + }); + } + } + + private function closeMultiHandle(?\Throwable &$failure): void + { + if ($this->multiHandle === null) { + return; + } + + $multiHandle = $this->multiHandle; + + $this->captureFailure($failure, function () use ($multiHandle): void { + try { + \curl_multi_close($multiHandle); + } finally { + $this->multiHandle = null; + } + }); + } + + private function closeOwnedFactory(?\Throwable &$failure): void + { + $factory = $this->factory; + if (!$this->ownsFactory || !$factory instanceof CurlFactory) { + return; + } + + $this->captureFailure($failure, static function () use ($factory): void { + $factory->close(); + }); + } + + /** + * @phpstan-impure + */ + private function executeMulti(): int + { + ++$this->multiExecDepth; + + try { + return \curl_multi_exec($this->getMultiHandle(), $this->active); + } finally { + --$this->multiExecDepth; + $this->finishDeferredWork(); + } + } + + /** + * Flushes attachments deferred while the multi handle was busy executing + * transfers or removing a handle, and cancels and a close deferred while + * it was executing transfers or processing completion messages. + */ + private function finishDeferredWork(): void + { + if ($this->multiExecDepth > 0 || $this->finishingDeferredWork) { + // A nested frame (a completion callback re-entered the handler) + // must not flush while an outer frame is still using the multi + // handle; the outermost frame flushes once it unwinds. + return; + } + + $this->finishingDeferredWork = true; + + try { + if (!$this->closed && !$this->closing) { + // Deferred adds only need native execution to be idle; a + // completion callback may drive and wait on them while the + // outermost message loop is still active. + $this->flushDeferredAdds(); + } + + if ($this->messageProcessingDepth > 0) { + // Cancels and a deferred close must wait for the outermost + // message loop to unwind. + return; + } + + $failure = null; + + // Removing a cancelled transfer runs its final progress update, + // whose callback can cancel other transfers, create requests, or + // close the handler; drain until no deferred work remains. + do { + $this->cleanupDeferredCancels($failure); + + if (!$this->closed && !$this->closing) { + $this->flushDeferredAdds(); + } + } while ($this->deferredCancels !== [] || (!$this->closed && !$this->closing && $this->deferredAdds !== [])); + + if ($this->deferredClose) { + $explicit = $this->deferredCloseExplicit; + + try { + $this->closeMultiHandle($failure); + $this->closeOwnedFactory($failure); + } finally { + $this->finishClose(); + } + + if ($explicit && $failure !== null) { + throw $failure; + } + + return; + } + + if ($failure !== null) { + throw $failure; + } + } finally { + $this->finishingDeferredWork = false; + } + } + + /** + * Attaches requests whose native attachment was deferred because they + * were created from inside a cURL callback. + */ + private function flushDeferredAdds(): void + { + if ($this->deferredAdds === []) { + return; + } + + $adds = $this->deferredAdds; + $this->deferredAdds = []; + + foreach ($adds as $id => $token) { + if (!$this->hasRequest($id, $token)) { + // Cancelled or replaced while the attachment was deferred. + continue; + } + + $entry = $this->handles[$id]; + + try { + $this->addHandleToMulti($id, $entry['easy']); + } catch (\Throwable $e) { + // The promise has already escaped, so reject it rather than + // throw. User code may have settled it directly; a settled + // promise must not abort the rest of the snapshot. + $rejection = $this->discardPendingRequest($id, $entry, $e); + if (P\State::isPending($entry['deferred'])) { + $entry['deferred']->reject($rejection); + } + } + } + } + + /** + * Fails a synchronous wait attempted from inside a cURL callback, where + * native execution cannot progress until the callback returns. + * + * @return string Why waiting stopped, for the caller to report when this + * method left the transfer unsettled + */ + private function failNestedWait(int $id, object $token): string + { + $stalled = $this->describeStalledWait($id, $token); + + if (!$this->hasRequest($id, $token)) { + return $stalled; + } + + $entry = $this->handles[$id]; + $message = 'Cannot synchronously wait for a transfer from inside a cURL callback on the same cURL multi handler; the callback must return before the transfer can progress.'; + $response = $entry['easy']->response; + $failure = $response !== null + ? new ResponseException($message, $entry['easy']->request, $response) + : new RequestException($message, $entry['easy']->request); + + if (!empty($entry['attached'])) { + // Native removal must wait until the outermost execution unwinds. + unset($this->handles[$id], $this->delays[$id], $this->deferredAdds[$id]); + $this->deferredCancels[$id] = ['easy' => $entry['easy'], 'attached' => true]; + } else { + $this->discardPendingRequest($id, $entry, $failure); + } + + $entry['deferred']->reject($failure); + + return $stalled; + } + + private function disposeEasyHandle( + #[\SensitiveParameter] + EasyHandle $easy + ): void { + if (!self::hasEasyHandle($easy)) { + return; + } + + $handle = $easy->handle; + unset($easy->handle); + + $failure = null; + + try { + $this->clearEasyHandleCallbacks($handle); + } catch (\Throwable $e) { + $failure = $e; + } + + try { + if (PHP_VERSION_ID < 80000 && \is_resource($handle)) { + \curl_close($handle); + } + } catch (\Throwable $e) { + if ($failure === null) { + $failure = $e; + } + } + + if ($failure !== null) { + throw $failure; + } + } + + /** + * @param resource|\CurlHandle $handle + */ + private function clearEasyHandleCallbacks( + #[\SensitiveParameter] + $handle + ): void { + curl_setopt($handle, \CURLOPT_HEADERFUNCTION, null); + curl_setopt($handle, \CURLOPT_READFUNCTION, null); + curl_setopt($handle, \CURLOPT_WRITEFUNCTION, null); + curl_setopt($handle, \CURLOPT_PROGRESSFUNCTION, null); + + if (\defined('CURLOPT_PREREQFUNCTION')) { + curl_setopt($handle, (int) \constant('CURLOPT_PREREQFUNCTION'), null); + } + + if (\defined('CURLOPT_SEEKFUNCTION')) { + curl_setopt($handle, (int) \constant('CURLOPT_SEEKFUNCTION'), null); + } + + if (\defined('CURLOPT_XFERINFOFUNCTION')) { + curl_setopt($handle, (int) \constant('CURLOPT_XFERINFOFUNCTION'), null); + } + } + + /** + * @param resource|\CurlHandle $handle + */ + private function removeHandleFromMulti( + int $id, + #[\SensitiveParameter] + $handle + ): void { + // Removing a still-running transfer performs a final progress update + // that can run a user progress callback, so removal is guarded like + // native execution. + ++$this->multiExecDepth; + + try { + \curl_multi_remove_handle($this->getMultiHandle(), $handle); + } finally { + --$this->multiExecDepth; + $this->unmarkProxyTunnelActiveById($id); + $this->finishDeferredWork(); + } + } + + private function hasMultiHandle(): bool + { + return $this->multiHandle !== null; + } + + private static function hasEasyHandle(EasyHandle $easy): bool + { + return \array_key_exists('handle', \get_object_vars($easy)); + } + + private function addRequest( + #[\SensitiveParameter] + array $entry + ): void { $easy = $entry['easy']; $id = (int) $easy->handle; + $entry['attached'] = false; + + $displaced = $this->handles[$id] ?? null; + if ($displaced !== null) { + // Never silently discard a tracked entry; settle it first. + unset($this->handles[$id], $this->delays[$id], $this->deferredAdds[$id]); + if (P\State::isPending($displaced['deferred'])) { + $message = \sprintf('cURL multi handler transfer %d was displaced by another request that reused its native cURL handle ID.', $id); + $response = $displaced['easy']->response; + $failure = $response !== null + ? new ResponseException($message, $displaced['easy']->request, $response) + : new RequestException($message, $displaced['easy']->request); + $displaced['deferred']->reject($failure); + } + } + $this->handles[$id] = $entry; - if (empty($easy->options['delay'])) { - \curl_multi_add_handle($this->_mh, $easy->handle); + + if (!empty($easy->options['delay'])) { + $this->delays[$id] = Clock::now() + ($easy->options['delay'] / 1000); + } elseif ($this->multiExecDepth > 0) { + // A request created from inside a cURL callback cannot be added + // natively while curl_multi_exec() is running; libcurl 7.59+ + // rejects the recursive call. Attach it once the outermost + // native execution unwinds. + $this->deferredAdds[$id] = $entry['wait_token'] ?? null; } else { - $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + $this->addHandleToMulti($id, $easy); } } /** * Cancels a handle from sending and removes references to it. * - * @param int $id Handle ID to cancel and remove. + * @param int $id Handle ID to cancel and remove. + * @param object|null $waitToken Identity token that must still match the + * entry when given. * * @return bool True on success, false on failure. */ - private function cancel($id): bool + private function cancel(int $id, ?object $waitToken = null): bool { - if (!is_int($id)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); - } - - // Cannot cancel if it has been processed. - if (!isset($this->handles[$id])) { + // Cannot cancel if it has been processed or replaced by a request + // that reused the native handle ID. + if (!$this->hasRequest($id, $waitToken)) { return false; } - $handle = $this->handles[$id]['easy']->handle; - unset($this->delays[$id], $this->handles[$id]); - \curl_multi_remove_handle($this->_mh, $handle); - \curl_close($handle); + $entry = $this->handles[$id]; + $easy = $entry['easy']; + $attached = !empty($entry['attached']); + unset($this->delays[$id], $this->deferredAdds[$id], $this->handles[$id]); + + if ($this->multiExecDepth > 0) { + $this->deferredCancels[$id] = ['easy' => $easy, 'attached' => $attached]; + + return true; + } + + $failure = null; + + if ($attached && $this->hasMultiHandle() && self::hasEasyHandle($easy)) { + $this->captureFailure($failure, function () use ($id, $easy): void { + $this->removeHandleFromMulti($id, $easy->handle); + }); + } + + $this->captureFailure($failure, function () use ($easy): void { + $this->disposeEasyHandle($easy); + }); + + if ($failure !== null) { + throw $failure; + } return true; } - private function processMessages(): void + private function cleanupDeferredCancels(?\Throwable &$failure): void { - while ($done = \curl_multi_info_read($this->_mh)) { - if ($done['msg'] !== \CURLMSG_DONE) { - // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 - continue; - } - $id = (int) $done['handle']; - \curl_multi_remove_handle($this->_mh, $done['handle']); + if ($this->deferredCancels === []) { + return; + } - if (!isset($this->handles[$id])) { - // Probably was cancelled. - continue; + $entries = $this->deferredCancels; + $this->deferredCancels = []; + + foreach ($entries as $id => $entry) { + $easy = $entry['easy']; + + if ($entry['attached'] && $this->hasMultiHandle() && self::hasEasyHandle($easy)) { + $this->captureFailure($failure, function () use ($id, $easy): void { + $this->removeHandleFromMulti($id, $easy->handle); + }); } - $entry = $this->handles[$id]; - unset($this->handles[$id], $this->delays[$id]); - $entry['easy']->errno = $done['result']; - $entry['deferred']->resolve( - CurlFactory::finish($this, $entry['easy'], $this->factory) - ); + $this->captureFailure($failure, function () use ($easy): void { + $this->disposeEasyHandle($easy); + }); } } - private function timeToNext(): int + private function processMessages(): void { - $currentTime = Utils::currentTime(); - $nextTime = \PHP_INT_MAX; + // CurlFactory::finish can retry a transfer by re-invoking this handler + // from inside this loop; the guard keeps that re-entry from recreating + // the multi handle mid-iteration (see applyProxyTunnelOwnership). A + // depth is tracked because a completion callback can re-enter tick(), + // and the nested frame must not clear the outer loop's guard. + ++$this->messageProcessingDepth; + + try { + // A completion callback may close the handler mid-loop; the close + // is deferred (closing is set first), and the loop must stop + // before touching the multi handle again. Remaining in-flight + // transfers were moved to the deferred cancels by deferClose(). + while (!$this->closed && !$this->closing) { + $done = \curl_multi_info_read($this->getMultiHandle()); + if (false === $done) { + break; + } + + if ($done['msg'] !== \CURLMSG_DONE) { + // If it is not done, removing the handle would be premature. + // See https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216. + continue; + } + if (!isset($done['handle'])) { + // Work around a PHP issue where cancelled transfers may omit the handle. + // Remove this once we no longer support PHP versions before the fix in + // https://github.com/php/php-src/pull/16302. + continue; + } + $id = (int) $done['handle']; + $this->removeHandleFromMulti($id, $done['handle']); + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + + // finish() can run completion callbacks that cancel this + // promise; a settled promise must not be settled again. + try { + $result = CurlFactory::finish($this, $entry['easy'], $this->factory); + } catch (\Throwable $e) { + if (P\State::isPending($entry['deferred'])) { + $entry['easy']->deferredSettled = true; + $entry['deferred']->reject($e); + } + + continue; + } + + if (P\State::isPending($entry['deferred'])) { + $entry['easy']->deferredSettled = true; + $entry['deferred']->resolve($result); + } + } + } finally { + --$this->messageProcessingDepth; + $this->finishDeferredWork(); + } + } + + /** + * Bounds a blocking select by the earliest pending request delay so a + * delayed transfer becoming due does not wait out an unrelated + * transfer's full select timeout. + */ + private function effectiveSelectTimeout(): float + { + if ($this->delays === []) { + return $this->selectTimeout; + } + + return \min($this->selectTimeout, $this->secondsToNext()); + } + + /** + * @return float Seconds until the earliest pending delay is due + */ + private function secondsToNext(): float + { + $currentTime = Clock::now(); + $nextTime = \PHP_FLOAT_MAX; foreach ($this->delays as $time) { if ($time < $nextTime) { $nextTime = $time; } } - return ((int) \max(0, $nextTime - $currentTime)) * 1000000; + return \max(0.0, $nextTime - $currentTime); + } + + private function timeToNext(): int + { + // PHP_INT_MAX first: min() then returns the int operand whenever the + // microseconds exceed it, so the cast never sees an oversized float. + return (int) \min(\PHP_INT_MAX, $this->secondsToNext() * 1000000); + } + + /** + * @return resource|\CurlMultiHandle + */ + private function getMultiHandle() + { + if ($this->multiHandle !== null) { + return $this->multiHandle; + } + + $this->assertOpen(); + + $multiHandle = \curl_multi_init(); + if (false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + + try { + foreach ($this->options as $option => $value) { + if (!\is_int($option)) { + throw new InvalidArgumentException(\sprintf('Invalid cURL multi option "%s".', Psr7\DiagnosticValue::escape((string) $option))); + } + + try { + $applied = @curl_multi_setopt($multiHandle, $option, $value); + } catch (\Throwable $e) { + throw new InvalidArgumentException( + \sprintf('Unable to apply the cURL multi option %s; it was rejected by the runtime libcurl.', self::formatCurlMultiOption($option)), + 0, + $e + ); + } + + if (true !== $applied) { + throw new InvalidArgumentException(\sprintf('Unable to apply the cURL multi option %s; it was rejected by the runtime libcurl.', self::formatCurlMultiOption($option))); + } + } + } catch (\Throwable $e) { + \curl_multi_close($multiHandle); + + throw $e; + } + + $this->multiHandle = $multiHandle; + + return $this->multiHandle; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php new file mode 100644 index 000000000..77bcc8b03 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php @@ -0,0 +1,276 @@ +mode = $mode; + $this->handle = $handle; + } + + /** + * @param mixed $sharing + */ + public static function fromOption($sharing): ?self + { + if ($sharing instanceof self) { + return $sharing; + } + + $mode = self::normalizeMode($sharing, 'transport_sharing'); + if ($mode === TransportSharing::NONE) { + return null; + } + + if ($mode === TransportSharing::HANDLER_PREFER) { + return self::createHandlerShareOrNull($mode); + } + + if ($mode === TransportSharing::HANDLER_REQUIRE) { + return self::createHandlerShare($mode); + } + + if ($mode === TransportSharing::PERSISTENT_PREFER) { + return self::createPersistentShareOrFallback(); + } + + return self::createPersistentShare($mode); + } + + /** + * @param mixed $sharing + */ + public static function normalizeMode($sharing, string $option): string + { + if ($sharing instanceof self) { + return $sharing->mode; + } + + if ($sharing === null || $sharing === TransportSharing::NONE) { + return TransportSharing::NONE; + } + + if ( + $sharing === TransportSharing::HANDLER_PREFER + || $sharing === TransportSharing::HANDLER_REQUIRE + || $sharing === TransportSharing::PERSISTENT_PREFER + || $sharing === TransportSharing::PERSISTENT_REQUIRE + ) { + return $sharing; + } + + throw new InvalidArgumentException(\sprintf( + 'The "%s" option must be null or a GuzzleHttp\\TransportSharing::* constant; received %s.', + $option, + \get_debug_type($sharing) + )); + } + + public static function assertNoRequiredSharingCustomFactoryConflict(array $options, string $handlerName): void + { + if (!\array_key_exists('handle_factory', $options) || $options['handle_factory'] === null) { + return; + } + + $mode = self::normalizeMode($options['transport_sharing'] ?? null, 'transport_sharing'); + if (!\in_array($mode, [TransportSharing::HANDLER_REQUIRE, TransportSharing::PERSISTENT_REQUIRE], true)) { + return; + } + + throw new InvalidArgumentException(\sprintf( + 'The "transport_sharing" %s option cannot require sharing with a custom "handle_factory" because Guzzle cannot ensure that the custom factory applies CURLOPT_SHARE.', + $handlerName + )); + } + + /** + * Whether cURL can share both the DNS and SSL session cache state that + * required handler-lifetime sharing needs, checked without configuring a + * share handle so default handler selection can fall back to the stream + * handler instead. + */ + public static function supportsHandlerRequireShare(): bool + { + return CurlVersion::supportsHandlerSharing() + && CurlVersion::supportsSslSessionSharing() + && \function_exists('curl_share_init') + && \function_exists('curl_share_setopt') + && \defined('CURLOPT_SHARE') + && \defined('CURLSHOPT_SHARE') + && \defined('CURL_LOCK_DATA_DNS') + && \defined('CURL_LOCK_DATA_SSL_SESSION'); + } + + private static function createHandlerShareOrNull(string $mode): ?self + { + try { + return self::createHandlerShare($mode); + } catch (\Throwable $e) { + return null; + } + } + + private static function createHandlerShare(string $mode): self + { + if (!\function_exists('curl_share_init') || !\function_exists('curl_share_setopt')) { + throw new InvalidArgumentException('The "transport_sharing" option requires cURL share support.'); + } + + self::requireCurlConstant('CURLOPT_SHARE'); + $shareOption = self::requireCurlConstant('CURLSHOPT_SHARE'); + $locks = self::handlerLocks($mode); + $handle = curl_share_init(); + + try { + foreach ($locks as $lock) { + try { + $success = curl_share_setopt($handle, $shareOption, $lock); + } catch (\Throwable $e) { + throw new InvalidArgumentException('Unable to configure cURL share handle: '.$e->getMessage(), 0, $e); + } + + if (!$success) { + throw new InvalidArgumentException(\sprintf('Unable to configure cURL share handle with lock data %d.', $lock)); + } + } + } catch (\Throwable $e) { + self::closeHandlerShareHandleOnPhp7($handle); + + throw $e; + } + + return new self($mode, $handle); + } + + private static function createPersistentShareOrFallback(): ?self + { + if (self::supportsPersistentShare()) { + try { + return self::createPersistentShare(TransportSharing::PERSISTENT_PREFER); + } catch (\Throwable $e) { + // Fall back to handler-lifetime best effort below. + } + } + + return self::createHandlerShareOrNull(TransportSharing::HANDLER_PREFER); + } + + private static function createPersistentShare(string $mode): self + { + CurlVersion::ensureConnectionSharingSupported(); + CurlVersion::ensureSslSessionSharingSupported(); + + if (!self::supportsPersistentShare()) { + throw new InvalidArgumentException('The "transport_sharing" option requires persistent cURL share handle support.'); + } + + self::requireCurlConstant('CURLOPT_SHARE'); + + try { + $handle = curl_share_init_persistent(self::persistentLocks()); + } catch (\Throwable $e) { + throw new InvalidArgumentException( + 'Unable to create persistent cURL share handle: '.$e->getMessage(), + 0, + $e + ); + } + + return new self($mode, $handle); + } + + private static function supportsPersistentShare(): bool + { + return CurlVersion::supportsConnectionSharing() + && CurlVersion::supportsSslSessionSharing() + && \function_exists('curl_share_init_persistent') + && \class_exists('CurlSharePersistentHandle') + && \defined('CURL_LOCK_DATA_DNS') + && \defined('CURL_LOCK_DATA_CONNECT') + && \defined('CURL_LOCK_DATA_SSL_SESSION'); + } + + /** + * @return int[] + */ + private static function handlerLocks(string $mode): array + { + CurlVersion::ensureHandlerSharingSupported(); + + if ($mode === TransportSharing::HANDLER_REQUIRE) { + CurlVersion::ensureSslSessionSharingSupported(); + } + + $locks = [ + self::requireCurlConstant('CURL_LOCK_DATA_DNS'), + ]; + + if (CurlVersion::supportsSslSessionSharing()) { + $locks[] = self::requireCurlConstant('CURL_LOCK_DATA_SSL_SESSION'); + } + + return $locks; + } + + /** + * @return int[] + */ + private static function persistentLocks(): array + { + return [ + self::requireCurlConstant('CURL_LOCK_DATA_DNS'), + self::requireCurlConstant('CURL_LOCK_DATA_CONNECT'), + self::requireCurlConstant('CURL_LOCK_DATA_SSL_SESSION'), + ]; + } + + private static function requireCurlConstant(string $constant): int + { + if (!\defined($constant)) { + throw new InvalidArgumentException(\sprintf( + 'The "transport_sharing" option requires %s, but it is not available in the installed PHP cURL extension.', + $constant + )); + } + + $value = \constant($constant); + if (!\is_int($value)) { + throw new InvalidArgumentException(\sprintf('The cURL constant %s must resolve to an integer.', $constant)); + } + + return $value; + } + + /** + * @param resource|\CurlShareHandle $handle + */ + private static function closeHandlerShareHandleOnPhp7($handle): void + { + if (\PHP_VERSION_ID < 80000 && \is_resource($handle)) { + curl_share_close($handle); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlVersion.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlVersion.php new file mode 100644 index 000000000..c3df944f0 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/CurlVersion.php @@ -0,0 +1,407 @@ +=') + && 0 !== (\CURL_VERSION_SSL & $versionInfo['features']); + } + + public static function supportsTls13(): bool + { + $version = self::get(); + + return \defined('CURL_SSLVERSION_TLSv1_3') + && null !== $version + && version_compare($version, self::TLS_13_VERSION, '>='); + } + + public static function supportsMultiplex(): bool + { + $version = self::get(); + + return \defined('CURLOPT_PIPEWAIT') + && null !== $version + && version_compare($version, self::MULTIPLEX_VERSION, '>='); + } + + public static function supportsHttpVersionReuseMatching(): bool + { + $version = self::get(); + + if (null === $version || version_compare($version, self::HTTP_VERSION_REUSE_MATCH_VERSION, '<')) { + return false; + } + + return version_compare($version, self::HTTP_VERSION_REUSE_MATCH_REGRESSION, '<') + || version_compare($version, self::HTTP_VERSION_REUSE_MATCH_RESTORED, '>='); + } + + public static function supportsRequiredHttp2Multiplex(): bool + { + $version = self::get(); + + return \defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE') + && null !== $version + && self::supportsHttp2() + && version_compare($version, self::REQUIRED_HTTP2_MULTIPLEX_VERSION, '>='); + } + + public static function supportsHttp2(): bool + { + $versionInfo = self::getVersionInfo(); + + // Requiring dependable CURLOPT_PIPEWAIT support keeps waiting from + // ever being silently unavailable where HTTP/2 works. + return \defined('CURL_VERSION_HTTP2') + && null !== $versionInfo + && self::supportsMultiplex() + && 0 !== (\CURL_VERSION_HTTP2 & $versionInfo['features']); + } + + public static function supportsHttp3(): bool + { + if (!\defined('CURL_VERSION_HTTP3') || !\defined('CURL_HTTP_VERSION_3') || !\defined('CURL_HTTP_VERSION_3ONLY')) { + return false; + } + + $versionInfo = self::getVersionInfo(); + if (null === $versionInfo || version_compare($versionInfo['version'], self::HTTP_3_VERSION, '<')) { + return false; + } + + return 0 !== ((int) \constant('CURL_VERSION_HTTP3') & $versionInfo['features']); + } + + public static function supportsRequiredHttp3Multiplex(): bool + { + $version = self::get(); + + return self::supportsHttp3() + && null !== $version + && version_compare($version, self::REQUIRED_HTTP3_MULTIPLEX_VERSION, '>='); + } + + public static function supportsHttpsProxy(): bool + { + $versionInfo = self::getVersionInfo(); + + return \defined('CURL_VERSION_HTTPS_PROXY') + && null !== $versionInfo + && version_compare($versionInfo['version'], self::HTTPS_PROXY_VERSION, '>=') + && 0 !== (\CURL_VERSION_HTTPS_PROXY & $versionInfo['features']); + } + + public static function supportsHandlerSharing(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::HANDLER_SHARING_VERSION, '>='); + } + + public static function ensureHandlerSharingSupported(): void + { + if (!self::supportsHandlerSharing()) { + throw new InvalidArgumentException(\sprintf( + 'The "transport_sharing" option requires libcurl %s or higher for cURL share handles.', + self::HANDLER_SHARING_VERSION + )); + } + } + + public static function supportsSslSessionSharing(): bool + { + $versionInfo = self::getVersionInfo(); + + return \defined('CURL_VERSION_SSL') + && null !== $versionInfo + && version_compare($versionInfo['version'], self::SSL_SESSION_SHARING_VERSION, '>=') + && 0 !== (\CURL_VERSION_SSL & $versionInfo['features']); + } + + public static function ensureSslSessionSharingSupported(): void + { + if (!self::supportsSslSessionSharing()) { + throw new InvalidArgumentException(\sprintf( + 'The "transport_sharing" option requires libcurl %s or higher with SSL support for SSL session sharing.', + self::SSL_SESSION_SHARING_VERSION + )); + } + } + + public static function supportsConnectionSharing(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::CONNECTION_SHARING_VERSION, '>='); + } + + public static function ensureConnectionSharingSupported(): void + { + if (!self::supportsConnectionSharing()) { + throw new InvalidArgumentException(\sprintf( + 'The "transport_sharing" option requires libcurl %s or higher for persistent connection sharing.', + self::CONNECTION_SHARING_VERSION + )); + } + } + + public static function supportsSharedPoolConnectionCaps(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::SHARED_POOL_CONNECTION_CAP_VERSION, '>='); + } + + public static function supportsShareConnectionCaches(): bool + { + $version = self::get(); + + // An undetectable libcurl version is treated as capable so the + // opaque share safeguards fail closed. + return null === $version + || version_compare($version, self::SHARE_CONNECTION_CACHE_VERSION, '>='); + } + + public static function supportsProxyTlsCredentialAwareConnectionReuse(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::PROXY_TLS_CREDENTIAL_REUSE_VERSION, '>='); + } + + public static function supportsProxyCredentialAwareConnectionReuse(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::PROXY_CREDENTIAL_REUSE_VERSION, '>='); + } + + public static function supportsSocksProxyCredentialAwareConnectionReuse(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::SOCKS_PROXY_CREDENTIAL_REUSE_VERSION, '>='); + } + + public static function supportsProxyHeaderSeparation(): bool + { + $version = self::get(); + + return null !== $version + && version_compare($version, self::PROXY_HEADER_SEPARATION_VERSION, '>=') + && \defined('CURLOPT_PROXYHEADER') + && \defined('CURLOPT_HEADEROPT') + && \defined('CURLHEADER_SEPARATE'); + } + + public static function supportsProtocolsStr(): bool + { + $version = self::get(); + + return \defined('CURLOPT_PROTOCOLS_STR') + && null !== $version + && version_compare($version, self::PROTOCOLS_STR_VERSION, '>='); + } + + public static function supportsProxyTunneling(): bool + { + $version = self::get(); + + return \defined('CURLOPT_SUPPRESS_CONNECT_HEADERS') + && null !== $version + && version_compare($version, self::PROXY_TUNNEL_VERSION, '>='); + } + + public static function ensureSupported( + #[\SensitiveParameter] + RequestInterface $request + ): void { + if (self::supportsCurlHandler()) { + return; + } + + $version = self::get(); + + if (null === $version || version_compare($version, self::MIN_VERSION, '<')) { + throw new ConnectException(\sprintf( + 'cURL %s or higher is required by the cURL handler; %s is installed.', + self::MIN_VERSION, + $version ?? 'an unknown version' + ), $request); + } + + if (!\defined('CURL_SSLVERSION_TLSv1_2')) { + throw new ConnectException(\sprintf( + 'The PHP cURL extension must be built against cURL %s or higher to use the cURL handler.', + self::MIN_VERSION + ), $request); + } + + if (!\defined('CURLMOPT_MAX_HOST_CONNECTIONS') || !\defined('CURLMOPT_MAX_TOTAL_CONNECTIONS')) { + throw new ConnectException('The PHP cURL extension must expose CURLMOPT_MAX_HOST_CONNECTIONS and CURLMOPT_MAX_TOTAL_CONNECTIONS to use the cURL handler.', $request); + } + + throw new ConnectException('The cURL handler requires libcurl SSL support.', $request); + } + + private static function get(): ?string + { + $versionInfo = self::getVersionInfo(); + + return null === $versionInfo ? null : $versionInfo['version']; + } + + /** + * @return array{version: string, features: int}|null + */ + private static function getVersionInfo(): ?array + { + if (null === self::$versionInfo) { + if (!\function_exists('curl_version')) { + self::$versionInfo = false; + } else { + $versionInfo = \curl_version(); + self::$versionInfo = \is_array($versionInfo) + && isset($versionInfo['version'], $versionInfo['features']) + && \is_string($versionInfo['version']) + && \is_int($versionInfo['features']) + ? [ + 'version' => $versionInfo['version'], + 'features' => $versionInfo['features'], + ] + : false; + } + } + + return false === self::$versionInfo ? null : self::$versionInfo; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/DeadlineSourceStream.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/DeadlineSourceStream.php new file mode 100644 index 000000000..122548dca --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/DeadlineSourceStream.php @@ -0,0 +1,82 @@ +stream = $stream; + $this->deadline = $deadline; + $this->readTimeout = $readTimeout; + } + + public function read(int $length): string + { + $idleStart = null; + + while (true) { + $remaining = $this->deadline - Clock::now(); + if ($remaining <= 0) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + + $data = $this->stream->read($length); + if ($data !== '') { + return $data; + } + if ($this->stream->eof()) { + return ''; + } + + $now = Clock::now(); + if ($idleStart === null) { + $idleStart = $now; + } + if ($this->readTimeout !== null && $now - $idleStart >= $this->readTimeout) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + + $wait = \min($remaining, self::POLL_INTERVAL); + if ($this->readTimeout !== null) { + $wait = \min($wait, $this->readTimeout - ($now - $idleStart)); + } + \usleep((int) \ceil($wait * 1e6)); + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/DeclaredLengthStream.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/DeclaredLengthStream.php new file mode 100644 index 000000000..7f0398b13 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/DeclaredLengthStream.php @@ -0,0 +1,103 @@ +stream = $stream; + $this->remaining = $declaredLength; + } + + public function __clone() + { + // Detaching a clone must not detach this body's transport. + $this->stream = clone $this->stream; + } + + public function eof(): bool + { + return $this->remaining === 0; + } + + public function isSeekable(): bool + { + return false; + } + + public function seek(int $offset, int $whence = \SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a response body bounded by its declared Content-Length'); + } + + /** + * @return mixed + */ + public function getMetadata(?string $key = null) + { + // A failed body has not timed out, whatever a stale flag on the + // transport says, so callers report the failure itself. + if ($this->failure !== null && $key === 'timed_out') { + return false; + } + + $metadata = $this->stream->getMetadata($key); + if ($this->failure !== null && $key === null && \is_array($metadata)) { + $metadata['timed_out'] = false; + } + + return $metadata; + } + + public function read(int $length): string + { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if ($this->failure !== null) { + throw $this->failure; + } + + if ($length === 0 || $this->remaining === 0) { + return ''; + } + + $data = $this->stream->read(\min($length, $this->remaining)); + if ($data === '' && $this->stream->eof()) { + $this->failure = new \RuntimeException('Response body ended before the declared Content-Length was reached'); + + throw $this->failure; + } + + $this->remaining -= \strlen($data); + + return $data; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php index 224344d7c..936562d29 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -1,10 +1,20 @@ Received HTTP headers so far */ - public $headers = []; + public array $headers = []; + + /** + * @var list Valid trailer lines, retained only when an + * on_trailers callback is configured + */ + public array $trailers = []; + + /** + * @var bool Whether this handle was configured with CURLOPT_PIPEWAIT + */ + public bool $usesPipewait = false; + + /** + * @var bool Whether processMessages() settled the deferred promise, which + * a rewind retry settles with a still-pending promise + */ + public bool $deferredSettled = false; /** * @var ResponseInterface|null Received response (if any) */ - public $response; - - /** - * @var RequestInterface Request being sent - */ - public $request; + public ?ResponseInterface $response = null; /** * @var array Request options */ - public $options = []; + public array $options = []; /** * @var int cURL error number (if any) */ - public $errno = 0; + public int $errno = 0; + + /** + * @var string|null Effective CURLOPT_PROXY value the handle was created with (if any) + */ + public ?string $effectiveProxy = null; + + /** + * Proxy tunnel or SOCKS proxy section signature for connection-reuse + * isolation, or null when the request does not require sectioning. + */ + public ?string $proxyTunnelSignature = null; /** * @var \Throwable|null Exception during on_headers (if any) */ - public $onHeadersException; + public ?\Throwable $onHeadersException = null; /** - * @var \Exception|null Exception during createResponse (if any) + * @var \Throwable|null Exception during progress callback (if any) */ - public $createResponseException; + public ?\Throwable $progressException = null; + + /** + * @var bool Whether the progress callback requested abort + */ + public bool $progressAborted = false; + + /** + * @var \Throwable|null Exception during createResponse (if any) + */ + public ?\Throwable $createResponseException = null; + + /** + * @var ResponseException|null Response header failure, if any. + */ + public ?ResponseException $responseHeaderException = null; + + /** + * @var TimeoutException|null Exception during request body read timeout. + */ + public ?TimeoutException $bodyReadTimeoutException = null; + + /** + * @var TimeoutException|null Exception during request body rewind timeout. + */ + public ?TimeoutException $bodyRewindTimeoutException = null; + + /** + * @var \Throwable|null Exception during request body rewind. + */ + public ?\Throwable $bodyRewindException = null; + + /** + * @var \Throwable|null Exception during request body read. + */ + public ?\Throwable $bodyReadException = null; + + /** + * @var TimeoutException|null Exception during response sink write timeout. + */ + public ?TimeoutException $sinkWriteTimeoutException = null; + + /** + * @var \Throwable|null Exception during response sink write. + */ + public ?\Throwable $sinkWriteException = null; + + /** + * @var bool Whether the response sink accepted a different byte count. + */ + public bool $sinkWriteIncomplete = false; + + /** + * @var int Number of response body bytes accepted by the sink. + */ + public int $responseBodyBytes = 0; + + /** + * @var \OverflowException|null Unrepresentable response body size or byte count. + */ + public ?\OverflowException $responseBodySizeException = null; /** * Attach a response to the easy handle based on the received headers. @@ -68,45 +162,100 @@ final class EasyHandle */ public function createResponse(): void { + $this->response = null; + $this->responseBodyBytes = 0; + $this->responseBodySizeException = null; + $this->responseHeaderException = null; + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers); - $normalizedKeys = Utils::normalizeHeaderKeys($headers); + // Non-101 informational responses precede the final response. Do not + // expose them as the response for a transfer that ends before the final + // response arrives. 101 switches protocol and is kept as terminal. + if ($status < 200 && $status !== 101) { + return; + } - if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) { + $framingFailure = null; + try { + $declaredLength = HeaderProcessor::validateResponseFraming($this->request->getMethod(), $status, $headers); + HeaderProcessor::assertContentLengthWithinPlatformLimit($declaredLength); + } catch (\RuntimeException $e) { + $framingFailure = $e; + } + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + $decodeContent = $this->options['decode_content'] ?? false; + if ($framingFailure === null && $decodeContent !== false && isset($normalizedKeys['content-encoding'])) { $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; unset($headers[$normalizedKeys['content-encoding']]); - if (isset($normalizedKeys['content-length'])) { - $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + $encodedContentLength = HeaderProcessor::removeHeader('Content-Length', $headers); + if ($encodedContentLength !== []) { + $headers['x-encoded-content-length'] = $encodedContentLength; - $bodyLength = (int) $this->sink->getSize(); + try { + $bodyLength = $this->sink->getSize(); + } catch (\Exception $e) { + $bodyLength = null; + } if ($bodyLength) { - $headers[$normalizedKeys['content-length']] = $bodyLength; - } else { - unset($headers[$normalizedKeys['content-length']]); + $headers['Content-Length'] = [(string) $bodyLength]; } } } - // Attach a response to the easy handle with the parsed headers. - $this->response = new Response( - $status, - $headers, - $this->sink, - $ver, - $reason - ); + // Attach a response to the easy handle with the parsed headers. Any + // exception propagates to the caller (CurlFactory), which records it as + // the createResponseException — do not catch it here. + $responseFactory = self::requireResponseFactory($this->options[RequestOptions::RESPONSE_FACTORY] ?? new HttpFactory()); + $response = $responseFactory->createResponse($status, $reason ?? '')->withProtocolVersion($ver); + foreach ($headers as $name => $value) { + $response = $response->withAddedHeader((string) $name, $value); + } + $this->response = $response->withBody($this->sink); + + if ($framingFailure instanceof \OverflowException) { + $this->responseHeaderException = new ResponseException( + $framingFailure->getMessage(), + $this->request, + $this->response, + $framingFailure + ); + } elseif ($framingFailure !== null) { + $this->responseHeaderException = new ResponseTransferException( + $framingFailure->getMessage(), + $this->request, + $this->response, + $framingFailure + ); + } + } + + /** + * @param mixed $factory + */ + private static function requireResponseFactory($factory): ResponseFactoryInterface + { + if (!$factory instanceof ResponseFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::RESPONSE_FACTORY, + ResponseFactoryInterface::class + )); + } + + return $factory; } /** - * @param string $name - * - * @return void - * * @throws \BadMethodCallException */ - public function __get($name) + public function __get(string $name): void { - $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; + $msg = $name === 'handle' + ? 'The EasyHandle has been released' + : \sprintf('Invalid property: %s', DiagnosticValue::escape($name)); + throw new \BadMethodCallException($msg); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EncodedBodyStream.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EncodedBodyStream.php new file mode 100644 index 000000000..ad648e64d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/EncodedBodyStream.php @@ -0,0 +1,83 @@ +stream = $stream; + + $limit = HeaderProcessor::contentLengthToInt($declaredLength); + $this->limit = $limit !== null && $limit > 0 ? $limit : null; + } + + public function eof(): bool + { + return ($this->limit !== null && $this->bytesRead >= $this->limit) + || $this->stream->eof(); + } + + public function isSeekable(): bool + { + return false; + } + + public function seek(int $offset, int $whence = \SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a stream while tracking encoded response bytes'); + } + + public function read(int $length): string + { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if ($this->limit !== null) { + $remaining = $this->limit - $this->bytesRead; + if ($remaining === 0) { + return ''; + } + + $length = \min($length, $remaining); + } + + $data = $this->stream->read($length); + $this->bytesRead = TransferByteCounter::add( + $this->bytesRead, + \strlen($data), + 'Response body exceeds the maximum integer size supported on this platform' + ); + + return $data; + } + + public function getBytesRead(): int + { + return $this->bytesRead; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php index a0988845f..cdd5d2c7d 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -1,7 +1,10 @@ \strlen($max) + || (\strlen($length) === \strlen($max) && \strcmp($length, $max) > 0) + ) { + return null; + } + + return (int) $length; + } + + public static function assertContentLengthWithinPlatformLimit(?string $length): void + { + if ($length === null || self::contentLengthToInt($length) !== null) { + return; + } + + throw new \OverflowException('Content-Length exceeds the maximum integer size supported on this platform'); + } + + /** + * Validates response framing and returns its normalized Content-Length. + * Returns null when absent or when ordinary body framing does not apply. + * + * @param array $headers + * + * @throws \RuntimeException when Content-Length is malformed, conflicting, + * or combined with Transfer-Encoding, or when a + * transfer coding is unsupported or follows + * chunked on the same field line + */ + public static function validateResponseFraming( + string $method, + int $status, + array $headers + ): ?string { + if (!self::responseCanHaveBody($method, $status)) { + return null; + } + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + $contentLength = self::removeHeader('Content-Length', $headers); + + try { + $length = self::parseContentLength($contentLength); + } catch (\RuntimeException $e) { + throw new \RuntimeException('Invalid Content-Length response header: '.$e->getMessage(), 0, $e); + } + + if ($length !== null && isset($normalizedKeys['transfer-encoding'])) { + throw new \RuntimeException('A response must not contain both Content-Length and Transfer-Encoding'); + } + + self::parseTransferEncoding($headers); + + return $length; + } + + /** + * Returns whether the response uses the chunked transfer coding. + * + * Guzzle never asks for a transfer coding, so like libcurl 8.14 and newer + * it accepts only identity and chunked, with nothing but chunked after + * chunked on the same field line. Empty list elements are ignored. + * + * @param array $headers + * + * @throws \RuntimeException when a coding is unsupported or follows chunked + * on the same field line + */ + public static function parseTransferEncoding(array $headers): bool + { + $chunked = false; + + foreach ($headers as $name => $values) { + if (!Psr7\Utils::caselessEquals((string) $name, 'Transfer-Encoding')) { + continue; + } + + foreach ($values as $value) { + $afterChunked = false; + foreach (\explode(',', $value) as $coding) { + $coding = \trim($coding, " \t"); + if ($coding === '') { + continue; + } + + if (Psr7\Utils::caselessEquals($coding, 'chunked')) { + $chunked = true; + $afterChunked = true; + } elseif ($afterChunked) { + throw new \RuntimeException('Invalid Transfer-Encoding response header: chunked is not the final transfer coding'); + } elseif (!Psr7\Utils::caselessEquals($coding, 'identity')) { + throw new \RuntimeException(\sprintf( + 'Invalid Transfer-Encoding response header: unsupported transfer coding "%s"', + Psr7\DiagnosticValue::escape($coding) + )); + } + } + } + } + + return $chunked; + } + + /** + * Removes every case-insensitive occurrence of a header and returns all + * removed values in their original field order. + * + * @param array $headers + * + * @return string[] Removed values across all header-name casings + */ + public static function removeHeader(string $name, array &$headers): array + { + $values = []; + + foreach ($headers as $key => $headerValues) { + if (Psr7\Utils::caselessEquals((string) $key, $name)) { + \array_push($values, ...$headerValues); + unset($headers[$key]); + } + } + + return $values; + } + + /** + * Whether a response uses ordinary body framing. A response to HEAD, a + * response with a 1xx, 204, or 304 status code, or a 2xx response to + * CONNECT never has a body, whatever its framing headers claim. A 205 + * remains subject to framing even though its semantics require no content. + */ + public static function responseCanHaveBody(string $method, int $status): bool + { + return $method !== 'HEAD' + && !($method === 'CONNECT' && $status >= 200 && $status < 300) + && $status >= 200 + && $status !== 204 + && $status !== 304; + } + + /** + * @param non-empty-list $headers + * + * @return list + */ + private static function getLastHeaderBlock(array $headers): array + { + $lastStatusLine = 0; + + foreach ($headers as $index => $line) { + if (self::isStatusLineCandidate($line)) { + $lastStatusLine = $index; + } + } + + return \array_slice($headers, $lastStatusLine); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HostValidator.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HostValidator.php new file mode 100644 index 000000000..1e74caf5d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/HostValidator.php @@ -0,0 +1,162 @@ +getUri()->getHost(), $request); + + foreach ($request->getHeader('Host') as $value) { + self::assertPrintableAscii((string) $value, 'The request Host header "%s" must contain only printable ASCII characters, because an intermediary or an origin server can otherwise read it as an authority that differs from the one the request names. An internationalized host name has an A-label form that this rule accepts.', $request); + } + } + + /** + * Only the URI host is reparsed for the connection. A Host header is sent + * verbatim and may carry a port, brackets, and an RFC 6874 zone identifier. + * + * @throws RequestException + */ + private static function assertUriHost( + string $host, + #[\SensitiveParameter] + RequestInterface $request + ): void { + self::assertPrintableAscii($host, 'The request URI host "%s" must contain only printable ASCII characters, because a handler can otherwise connect to a host that differs from the one the request names. An internationalized host name has an A-label form that this rule accepts.', $request); + + if (\strpos($host, '%') !== false) { + throw new RequestException(\sprintf('The request URI host "%s" must not contain a percent escape, because a handler decodes it and can then connect to a host that differs from the one the request names.', self::escape($host)), $request); + } + + // GuzzleHttp\Psr7\Uri already enforces this predicate, so it only + // rejects invalid third-party UriInterface values. + if (!Psr7\Rfc3986::isValidHost($host)) { + throw new RequestException(\sprintf('The request URI host "%s" must be a valid RFC 3986 host, because a handler reparses the URI and can then connect to a host that differs from the one the request names.', self::escape($host)), $request); + } + + // libcurl 8.21.0 drops a trailing dot from inet_aton-style numeric + // hosts before connecting, while other readers treat the input as a + // name. Test the shape rather than the range so malformed numeric + // values fail closed. rtrim() also covers multiple trailing dots if + // libcurl later relaxes its current guard. Plain shorthand stays + // accepted. HostIdentity uses a broader grammar for cookie matching. + // + // @see \GuzzleHttp\HostIdentity::canonicalHost() + if (\str_ends_with($host, '.') && self::isNumericIpv4Host(\rtrim($host, '.'))) { + throw new RequestException(\sprintf('The request URI host "%s" must not be written as one to four decimal, octal or hexadecimal parts followed by one or more trailing dots, because a handler can read that spelling as an IPv4 address and connect to that address while the rest of the process reads a name.', self::escape($host)), $request); + } + } + + /** + * Reports whether a host has libcurl's inet_aton-style shape: one to four + * decimal, 0-prefixed octal, or 0x-prefixed hexadecimal parts. + * + * Range and 32-bit overflow checks are deliberately omitted. This may + * reject a trailing-dot spelling the transport reads as a name, but avoids + * missing one it resolves as an address. + */ + public static function isNumericIpv4Host(string $host): bool + { + if ($host === '') { + return false; + } + + $parts = \explode('.', $host); + + if (\count($parts) > 4) { + return false; + } + + foreach ($parts as $part) { + if (!self::isNumericIpv4Part($part)) { + return false; + } + } + + return true; + } + + private static function isNumericIpv4Part(string $part): bool + { + if ($part === '') { + return false; + } + + if ($part[0] === '0' && isset($part[1]) && ($part[1] === 'x' || $part[1] === 'X')) { + return \strlen($part) > 2 && \strspn($part, '0123456789abcdefABCDEF', 2) === \strlen($part) - 2; + } + + $digits = $part[0] === '0' ? '01234567' : '0123456789'; + + return \strspn($part, $digits) === \strlen($part); + } + + /** + * @throws RequestException + */ + private static function assertPrintableAscii( + string $value, + string $message, + #[\SensitiveParameter] + RequestInterface $request + ): void { + // Match positively so a PCRE failure rejects. + if (\preg_match('/\A[\x21-\x7E]*\z/D', $value) !== 1) { + throw new RequestException(\sprintf($message, self::escape($value)), $request); + } + } + + /** + * Escapes non-printable bytes as uppercase \xNN for safe diagnostics. + * + * Psr7\DiagnosticValue is not used because it preserves valid non-ASCII + * UTF-8, including invisible characters rejected here. + */ + private static function escape(string $value): string + { + $escaped = ''; + + for ($offset = 0, $length = \strlen($value); $offset < $length; ++$offset) { + $byte = \ord($value[$offset]); + if ($byte >= 0x20 && $byte <= 0x7E) { + $escaped .= $value[$offset]; + + continue; + } + + $escaped .= \sprintf('\\x%02X', $byte); + } + + return $escaped; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/InflateBodyStream.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/InflateBodyStream.php new file mode 100644 index 000000000..2d92df892 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/InflateBodyStream.php @@ -0,0 +1,330 @@ +stream = $stream; + $this->deflate = $deflate; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + public function close(): void + { + $stream = $this->stream; + $this->release(); + + if ($stream !== null) { + $stream->close(); + } + } + + public function detach() + { + if ($this->stream === null || !$this->detachable) { + return null; + } + + // Hand the decoder over to a resource that keeps yielding decoded + // bytes. That decoder cannot be detached again, so casting the resource + // to a descriptor fails instead of recursing. + $stream = clone $this; + $stream->detachable = false; + $this->release(); + + return StreamWrapper::getResource($stream); + } + + public function getSize(): ?int + { + return null; + } + + public function tell(): int + { + $this->attached(); + + return $this->position; + } + + public function eof(): bool + { + $stream = $this->attached(); + + // A failed body never ends, so resource readers see the failure + // instead of a clean end of the stream. + return $this->error === null && $this->offset === \strlen($this->buffer) && $stream->eof(); + } + + public function isSeekable(): bool + { + return false; + } + + public function seek(int $offset, int $whence = \SEEK_SET): void + { + throw new \RuntimeException('Stream is not seekable'); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + public function isReadable(): bool + { + return $this->stream !== null && $this->stream->isReadable(); + } + + public function read(int $length): string + { + $stream = $this->attached(); + + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if ($length === 0) { + return ''; + } + + while ($this->offset === \strlen($this->buffer)) { + if ($this->error !== null) { + throw $this->error; + } + + $encoded = $stream->read(self::CHUNK_SIZE); + if ($encoded === '') { + return ''; + } + + if ($this->finished) { + if ($this->allowTrailing(\strlen($encoded))) { + continue; + } + + throw $this->fail(self::TRAILING_DATA); + } + + $this->buffer = $this->inflate($encoded); + $this->offset = 0; + } + + // Slice by offset, since a single inflate_add() call can return + // megabytes of highly compressible data. + $data = (string) \substr($this->buffer, $this->offset, $length); + $this->offset += \strlen($data); + $this->position = TransferByteCounter::add( + $this->position, + \strlen($data), + 'Response body exceeds the maximum integer size supported on this platform' + ); + + return $data; + } + + /** + * @return mixed + */ + public function getMetadata(?string $key = null) + { + if ($this->stream === null) { + return $key === null ? [] : null; + } + + // A decoding failure is not a timeout, even when Windows leaves the + // transport's timed_out flag set after a read drains PHP's buffer. + if ($key === 'timed_out' && $this->error !== null) { + return false; + } + + $metadata = $this->stream->getMetadata($key); + if ($key === null && $this->error !== null && \is_array($metadata)) { + $metadata['timed_out'] = false; + } + + return $metadata; + } + + private function inflate(string $encoded, ?int $encoding = null): string + { + $retry = false; + if ($this->context === null) { + $this->header .= $encoded; + if (\strlen($this->header) < 2) { + return ''; + } + + $encoded = $this->header; + $this->header = ''; + + if (!\function_exists('inflate_init')) { + throw $this->fail('Unable to decode the response body: the zlib extension is not loaded'); + } + + // inflate_init() has no automatic header detection, so pick the + // format the way zlib does for the window bits 15 + 32 that the + // zlib.inflate filter is given. + $encoding = $encoding ?? (\strncmp($encoded, "\x1f\x8b", 2) === 0 ? \ZLIB_ENCODING_GZIP : \ZLIB_ENCODING_DEFLATE); + $retry = $this->deflate && $encoding === \ZLIB_ENCODING_DEFLATE; + $context = \inflate_init($encoding); + if ($context === false) { + throw $this->fail(self::INVALID_DATA); + } + + $this->context = $context; + } + + $readBefore = \inflate_get_read_len($this->context); + + // Application error handlers also see @-suppressed warnings, so keep + // the warning inflate_add() emits before returning false from them. + \set_error_handler(static function (): bool { + return true; + }); + + try { + $decoded = \inflate_add($this->context, $encoded); + } finally { + \restore_error_handler(); + } + + if ($decoded === false) { + // Like libcurl, decode deflate data that zlib rejects in the first + // call as raw deflate, which may be followed by up to 4 unknown + // bytes such as the checksum of a zlib stream missing its header. + if ($retry) { + $this->context = null; + $this->trailingAllowance = 4; + + return $this->inflate($encoded, \ZLIB_ENCODING_RAW); + } + + throw $this->fail(self::INVALID_DATA); + } + + $this->finished = \inflate_get_status($this->context) === \ZLIB_STREAM_END; + if ($this->finished) { + $consumed = \inflate_get_read_len($this->context) - $readBefore; + if ($consumed < 0) { + // zlib counts input in an unsigned long, which wraps at 4 GiB + // on Windows and 32-bit builds. + $consumed += 0x100000000; + } + + if (!$this->allowTrailing(\strlen($encoded) - (int) $consumed)) { + // Throw only after the bytes decoded so far have been read. + $this->fail(self::TRAILING_DATA); + } + } + + return $decoded; + } + + private function allowTrailing(int $length): bool + { + if ($length > $this->trailingAllowance) { + return false; + } + + $this->trailingAllowance -= $length; + + return true; + } + + private function attached(): StreamInterface + { + if ($this->stream === null) { + throw new \RuntimeException('Stream is detached'); + } + + return $this->stream; + } + + private function release(): void + { + $this->stream = null; + $this->context = null; + $this->buffer = ''; + $this->offset = 0; + } + + private function fail(string $message): \RuntimeException + { + return $this->error = new \RuntimeException($message); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php index 79664e279..9d80e8de6 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -1,46 +1,46 @@ |callable(RequestInterface, array): (ResponseInterface|\Throwable|PromiseInterface)> */ - private $lastRequest; + private array $queue = []; + + private ?RequestInterface $lastRequest = null; /** - * @var array + * @var array */ - private $lastOptions = []; + private array $lastOptions = []; /** - * @var callable|null + * @var (callable(ResponseInterface|null): mixed)|null */ private $onFulfilled; /** - * @var callable|null + * @var (callable(mixed): mixed)|null */ private $onRejected; @@ -48,26 +48,35 @@ class MockHandler implements \Countable * Creates a new MockHandler that uses the default handler stack list of * middlewares. * - * @param array|null $queue Array of responses, callables, or exceptions. - * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. - * @param callable|null $onRejected Callback to invoke when the return value is rejected. + * @param array|callable(RequestInterface, array): (ResponseInterface|\Throwable|PromiseInterface)>|null $queue Array of responses, promises, callables, or throwables. + * @param (callable(ResponseInterface|null): mixed)|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param (callable(mixed): mixed)|null $onRejected Callback to invoke when the return value is rejected. + * + * @return HandlerStack): PromiseInterface> */ - public static function createWithMiddleware(array $queue = null, callable $onFulfilled = null, callable $onRejected = null): HandlerStack - { + public static function createWithMiddleware( + #[\SensitiveParameter] + ?array $queue = null, + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): HandlerStack { return HandlerStack::create(new self($queue, $onFulfilled, $onRejected)); } /** * The passed in value must be an array of - * {@see \Psr\Http\Message\ResponseInterface} objects, Exceptions, - * callables, or Promises. + * {@see ResponseInterface} objects, throwables, callables, or promises. * - * @param array|null $queue The parameters to be passed to the append function, as an indexed array. - * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. - * @param callable|null $onRejected Callback to invoke when the return value is rejected. + * @param array|callable(RequestInterface, array): (ResponseInterface|\Throwable|PromiseInterface)>|null $queue The parameters to be passed to the append function, as an indexed array. + * @param (callable(ResponseInterface|null): mixed)|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param (callable(mixed): mixed)|null $onRejected Callback to invoke when the return value is rejected. */ - public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) - { + public function __construct( + #[\SensitiveParameter] + ?array $queue = null, + ?callable $onFulfilled = null, + ?callable $onRejected = null + ) { $this->onFulfilled = $onFulfilled; $this->onRejected = $onRejected; @@ -77,30 +86,41 @@ class MockHandler implements \Countable } } - public function __invoke(RequestInterface $request, array $options): PromiseInterface - { + /** + * @return PromiseInterface + */ + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { if (!$this->queue) { + // Test-setup error (more requests made than responses queued); + // intentionally a bare SPL exception, not a GuzzleException. throw new \OutOfBoundsException('Mock queue is empty'); } if (isset($options['delay']) && \is_numeric($options['delay'])) { - \usleep((int) $options['delay'] * 1000); + Clock::sleep((int) ($options['delay'] * 1000)); + } + + if (isset($options['on_stats']) && !\is_callable($options['on_stats'])) { + throw new InvalidArgumentException('on_stats must be callable'); } $this->lastRequest = $request; $this->lastOptions = $options; $response = \array_shift($this->queue); + $onHeaders = null; + $onHeadersResponse = null; if (isset($options['on_headers'])) { if (!\is_callable($options['on_headers'])) { - throw new \InvalidArgumentException('on_headers must be callable'); - } - try { - $options['on_headers']($response); - } catch (\Exception $e) { - $msg = 'An error was encountered during the on_headers event'; - $response = new RequestException($msg, $request, $response, $e); + throw new InvalidArgumentException('on_headers must be callable'); } + + $onHeaders = $options['on_headers']; } if (\is_callable($response)) { @@ -111,8 +131,36 @@ class MockHandler implements \Countable ? P\Create::rejectionFor($response) : P\Create::promiseFor($response); - return $response->then( - function (?ResponseInterface $value) use ($request, $options) { + if (\is_callable($onHeaders)) { + $response = $response->then( + static function ( + #[\SensitiveParameter] + $value + ) use ($onHeaders, $request, &$onHeadersResponse) { + if (!$value instanceof ResponseInterface) { + return $value; + } + + try { + $onHeaders($value, $request); + } catch (\Throwable $e) { + $msg = 'An error was encountered during the on_headers event'; + $onHeadersResponse = $value; + + throw new ResponseException($msg, $request, $value, $e); + } + + return $value; + } + ); + } + + $promise = $response->then( + function ( + #[\SensitiveParameter] + $value + ) use ($request, $options): ?ResponseInterface { + /** @var ResponseInterface|null $value */ $this->invokeStats($request, $options, $value); if ($this->onFulfilled) { ($this->onFulfilled)($value); @@ -133,24 +181,33 @@ class MockHandler implements \Countable return $value; }, - function ($reason) use ($request, $options) { - $this->invokeStats($request, $options, null, $reason); + function ( + #[\SensitiveParameter] + $reason + ) use ($request, $options, &$onHeadersResponse): PromiseInterface { + $this->invokeStats($request, $options, $onHeadersResponse, $reason); if ($this->onRejected) { ($this->onRejected)($reason); } + return P\Create::rejectionFor($reason); } ); + + /** @var PromiseInterface $promise */ + return $promise; } /** * Adds one or more variadic requests, exceptions, callables, or promises * to the queue. * - * @param mixed ...$values + * @param mixed ...$values Responses, promises, throwables, or request-aware callables. */ - public function append(...$values): void - { + public function append( + #[\SensitiveParameter] + ...$values + ): void { foreach ($values as $value) { if ($value instanceof ResponseInterface || $value instanceof \Throwable @@ -159,7 +216,7 @@ class MockHandler implements \Countable ) { $this->queue[] = $value; } else { - throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value)); + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found '.\get_debug_type($value)); } } } @@ -197,14 +254,22 @@ class MockHandler implements \Countable * @param mixed $reason Promise or reason. */ private function invokeStats( + #[\SensitiveParameter] RequestInterface $request, + #[\SensitiveParameter] array $options, - ResponseInterface $response = null, + #[\SensitiveParameter] + ?ResponseInterface $response = null, + #[\SensitiveParameter] $reason = null ): void { if (isset($options['on_stats'])) { - $transferTime = $options['transfer_time'] ?? 0; - $stats = new TransferStats($request, $response, $transferTime, $reason); + $transferTime = $options['transfer_time'] ?? 0.0; + if (!\is_int($transferTime) && !\is_float($transferTime) && (!\is_string($transferTime) || !\is_numeric($transferTime))) { + throw new InvalidArgumentException('transfer_time must be a number of seconds'); + } + + $stats = new TransferStats($request, $response, (float) $transferTime, $reason); ($options['on_stats'])($stats); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php index f045b526c..324351d78 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -1,30 +1,40 @@ ): PromiseInterface $default Handler used for normal responses + * @param callable(RequestInterface, array): PromiseInterface $sync Handler used for synchronous responses. * - * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler. + * @return callable(RequestInterface, array): PromiseInterface Returns the composed handler. */ public static function wrapSync(callable $default, callable $sync): callable { - return static function (RequestInterface $request, array $options) use ($default, $sync): PromiseInterface { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($default, $sync): PromiseInterface { return empty($options[RequestOptions::SYNCHRONOUS]) ? $default($request, $options) : $sync($request, $options); }; } @@ -37,14 +47,19 @@ class Proxy * performance benefits of curl while still supporting true streaming * through the StreamHandler. * - * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for non-streaming responses - * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses + * @param callable(RequestInterface, array): PromiseInterface $default Handler used for non-streaming responses + * @param callable(RequestInterface, array): PromiseInterface $streaming Handler used for streaming responses * - * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler. + * @return callable(RequestInterface, array): PromiseInterface Returns the composed handler. */ public static function wrapStreaming(callable $default, callable $streaming): callable { - return static function (RequestInterface $request, array $options) use ($default, $streaming): PromiseInterface { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($default, $streaming): PromiseInterface { return empty($options['stream']) ? $default($request, $options) : $streaming($request, $options); }; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/ProxyEnv.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/ProxyEnv.php new file mode 100644 index 000000000..f0147ea0e --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/ProxyEnv.php @@ -0,0 +1,159 @@ +hasProxy() || $selection->shouldDisableProxy()) { + return $selection; + } + + $envProxy = self::getProxyForScheme($uri->getScheme()); + if ($envProxy === null) { + return $selection; + } + + $noProxy = self::getNoProxy(); + if ($noProxy !== null && ProxyOptions::isUriInNoProxy($uri, self::splitNoProxy($noProxy))) { + return ProxySelection::bypassed(); + } + + // $envProxy is never '' (empty env values are treated as unset). + return ProxySelection::proxy($envProxy); + } + + /** + * Resolves the proxy to use for the given request scheme. + * + * The lookup mirrors libcurl for the http and https schemes the handlers + * accept: the lowercase scheme-specific variable first, its uppercase + * variant next (except for "http", where uppercase HTTP_PROXY is never + * read), then all_proxy/ALL_PROXY. + * + * @return string|null The proxy to use; null when the environment + * configures none. + */ + public static function getProxyForScheme(string $scheme): ?string + { + $scheme = Psr7\Utils::asciiToLower($scheme); + $candidates = [$scheme.'_proxy']; + if ($scheme !== 'http') { + // Uppercase HTTP_PROXY is deliberately never consulted: a CGI + // request header "Proxy:" becomes HTTP_PROXY in the environment. + // See https://httpoxy.org for more information. + $candidates[] = Psr7\Utils::asciiToUpper($scheme).'_PROXY'; + } + $candidates[] = 'all_proxy'; + $candidates[] = 'ALL_PROXY'; + + foreach ($candidates as $name) { + $value = self::getenv($name); + if ($value !== null) { + return $value; + } + } + + return null; + } + + /** + * @return string|null The no-proxy list; null when nothing is set. + */ + public static function getNoProxy(): ?string + { + foreach (['no_proxy', 'NO_PROXY'] as $name) { + $value = self::getenv($name); + if ($value !== null) { + return $value; + } + } + + return null; + } + + /** + * Splits a no_proxy environment value into matchable entries. + * + * Entries may be separated by commas or blanks, the way libcurl tokenizes + * the value. + * + * @return string[] + */ + public static function splitNoProxy(string $noProxy): array + { + $entries = []; + + $split = \preg_split('/[\s,]+/', $noProxy); + + if ($split === false) { + throw new \RuntimeException('Unable to split the no_proxy value: '.\preg_last_error_msg()); + } + + foreach ($split as $entry) { + if ($entry !== '') { + $entries[] = $entry; + } + } + + return $entries; + } + + private static function getenv(string $name): ?string + { + // Windows environment variables are case-insensitive, so the + // lowercase-only httpoxy defence does not hold there. Outside the + // CLI SAPI on Windows, environment proxies are not resolved at all + // (a safe-side divergence from libcurl). + if (\PHP_OS_FAMILY === 'Windows' && \PHP_SAPI !== 'cli') { + return null; + } + + // local_only: the OS environment and putenv() only - the same + // environ(7) libcurl reads. SAPI request environments such as + // fastcgi_param or SetEnv are deliberately excluded. + $value = \getenv($name, true); + + // libcurl's GetEnv (lib/getenv.c) treats variables set to an empty + // string as unset on every version, so the lookup falls through to + // the next candidate. + return $value === false || $value === '' ? null : $value; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/RequestFraming.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/RequestFraming.php new file mode 100644 index 000000000..c9772dc37 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/RequestFraming.php @@ -0,0 +1,256 @@ +request = $request; + $this->bodySize = $bodySize; + $this->contentLength = $contentLength; + } + + /** + * Finalizes request framing for a handler dispatch. + * + * When $sendBody is false, body-dependent validation and size probing are + * skipped. Content-Length is still canonicalized and Transfer-Encoding is + * removed. + * + * @throws RequestException when framing or body metadata is unsafe + */ + public static function analyze( + #[\SensitiveParameter] + RequestInterface $request, + bool $sendBody = true + ): self { + try { + $length = HeaderProcessor::parseContentLength($request->getHeader('Content-Length')); + } catch (\RuntimeException $e) { + throw new RequestException('Invalid Content-Length request header: '.$e->getMessage(), $request, 0, $e); + } + + try { + HeaderProcessor::assertContentLengthWithinPlatformLimit($length); + } catch (\OverflowException $e) { + throw new RequestException($e->getMessage(), $request, 0, $e); + } + + $contentLength = HeaderProcessor::contentLengthToInt($length); + if ($length !== null) { + $request = $request->withHeader('Content-Length', $length); + } + + if (!$sendBody) { + return new self($request->withoutHeader('Transfer-Encoding'), null, $contentLength); + } + + $transferEncoding = $request->getHeader('Transfer-Encoding'); + if ($length !== null && $transferEncoding !== []) { + throw new RequestException( + 'A request must not contain both Content-Length and Transfer-Encoding', + $request + ); + } + + if ($transferEncoding !== []) { + $tokens = []; + foreach ($transferEncoding as $value) { + foreach (\explode(',', $value) as $part) { + $tokens[] = \trim($part, " \t"); + } + } + + if (\count($tokens) !== 1 || !Psr7\Utils::caselessEquals($tokens[0], 'chunked') || $request->getProtocolVersion() !== '1.1') { + throw new RequestException('Unsupported Transfer-Encoding request header', $request); + } + } + + $bodySize = self::bodySize($request); + if ($bodySize !== null && $contentLength !== null && $bodySize !== $contentLength) { + throw new RequestException('Content-Length does not match the request body size', $request); + } + + if ($bodySize === null && $contentLength === null && $request->getProtocolVersion() === '1.0') { + throw new RequestException('An unknown-size HTTP/1.0 request body requires Content-Length', $request); + } + + if ($length === null && $bodySize !== null && ($bodySize > 0 || \in_array($request->getMethod(), ['PUT', 'POST'], true))) { + $contentLength = $bodySize; + $request = $request->withHeader('Content-Length', (string) $bodySize); + } + + return new self($request->withoutHeader('Transfer-Encoding'), $bodySize, $contentLength); + } + + /** + * Returns the number of bytes available for dispatch, or null when unknown. + * + * Seekable bodies use their total size because handlers rewind them. + * Positioned non-seekable bodies use the total size minus their current + * position. + * + * @throws RequestException when body metadata cannot be read safely + */ + public static function bodySize( + #[\SensitiveParameter] + RequestInterface $request + ): ?int { + $body = $request->getBody(); + + try { + $size = $body->getSize(); + if ($size === null) { + return null; + } + + if ($size < 0) { + throw new \RuntimeException('Request body size must not be negative'); + } + + $seekable = $body->isSeekable(); + } catch (\Exception $e) { + throw self::bodyException( + $request, + $e, + 'Timed out while determining the request body size', + 'Failed to determine the request body size' + ); + } + + if ($seekable) { + return $size; + } + + try { + $position = $body->tell(); + } catch (\RuntimeException $e) { + return null; + } + + if ($position < 0 || $position > $size) { + throw new RequestException('The request body position is outside the stream size', $request); + } + + return $size - $position; + } + + /** + * Materializes the body, stopping at the selected Content-Length when + * present. + * + * @throws RequestException when the body cannot be fully rewound or read + */ + public function materialize(): string + { + $body = $this->request->getBody(); + + if ($this->contentLength === null) { + try { + return (string) $body; + } catch (\Exception $e) { + throw self::bodyException( + $this->request, + $e, + 'Timed out while reading the request body', + 'Failed to read the request body' + ); + } + } + + if ($this->contentLength === 0) { + return ''; + } + + try { + if ($body->isSeekable()) { + $body->rewind(); + } + } catch (\Exception $e) { + throw self::bodyException( + $this->request, + $e, + 'Timed out while rewinding the request body', + 'Failed to rewind the request body' + ); + } + + $contents = ''; + $remaining = $this->contentLength; + while ($remaining > 0) { + $limit = \min(8192, $remaining); + + try { + $chunk = $body->read($limit); + } catch (\Exception $e) { + throw self::bodyException( + $this->request, + $e, + 'Timed out while reading the request body', + 'Failed to read the request body' + ); + } + + if ($chunk === '') { + throw new RequestException( + 'Request body ended before the declared Content-Length was reached', + $this->request + ); + } + + if (\strlen($chunk) > $limit) { + throw new RequestException('Request body stream returned more bytes than requested', $this->request); + } + + $contents .= $chunk; + $remaining -= \strlen($chunk); + } + + return $contents; + } + + private static function bodyException( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + \Exception $exception, + string $timeoutMessage, + string $fallbackMessage + ): RequestException { + $message = $exception instanceof TimeoutException + ? $timeoutMessage + : ($exception->getMessage() !== '' ? $exception->getMessage() : $fallbackMessage); + + return new RequestException($message, $request, 0, $exception); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php index 543f825a2..3bcc80bce 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php @@ -1,239 +1,898 @@ true, + 'max_total_connections' => true, + 'transport_sharing' => true, + ]; + /** - * @var array + * Peers tracked by the TLS session cache, matching libcurl's share handle + * TLS session cache (25 peers, up to 2 sessions each). */ - private $lastHeaders = []; + private const TLS_SESSION_CACHE_MAX_KEYS = 25; + + private const CONNECTION_ERRORS = [ + 'php_network_getaddresses:', + 'getaddrinfo', + 'gethostbyname failed', + 'Unable to connect to', + 'Connection refused', + 'No connection could be made because the target machine actively refused it', + 'connection attempt failed', + 'connect() failed', + 'Network is unreachable', + 'No route to host', + 'Host is unreachable', + 'Host is down', + 'Cannot connect to HTTPS server through proxy', + 'Failed to enable crypto', + ]; + + private const CONNECT_TIMEOUT_ERRORS = [ + 'Connection timed out', + 'Operation timed out', + 'SSL: Handshake timed out', + 'did not properly respond after a period of time', + ]; + + private const NETWORK_ERRORS = [ + 'SSL: Connection reset by peer', + 'SSL: Broken pipe', + 'unexpected eof while reading', + ]; + + // PHP's HTTP wrapper can collapse wrapper-level errors to OpenFailed. + // These codes classify only errors delivered directly to the context + // handler. NetworkRecvFailed is omitted because PHP currently emits it for + // handshake-time certificate policy failures. + private const STRUCTURED_NETWORK_ERROR_CODES = [ + 'NetworkSendFailed', + ]; + + // SslNotSupported can be emitted directly by a transport that cannot + // enable crypto. Standard ext-openssl proxy failures are normally + // collapsed to OpenFailed and continue to use message classification. + private const STRUCTURED_CONNECTION_ERROR_CODES = [ + 'SslNotSupported', + ]; + + /** + * Default idle timeout in milliseconds when the "read_timeout" option is + * not set. Matches PHP's default_socket_timeout default, which the + * handler never consults. + */ + private const DEFAULT_IDLE_TIMEOUT_MS = 60000; + + private ?\Throwable $onStatsException = null; + + private string $transportSharingMode; + + private bool $connectionCapsConfigured = false; + + private ?StreamTlsSessionCache $sessionCache = null; + + /** + * Accepts an associative array of options: + * + * - max_host_connections: Optional positive integer or null. A non-null + * value marks the handler as incompatible with enabled response + * streaming; the number is not used for stream-handler admission. + * - max_total_connections: Optional positive integer or null. A non-null + * value marks the handler as incompatible with enabled response + * streaming; the number is not used for stream-handler admission. + * - transport_sharing: Optional transport sharing mode. + * + * The stream handler cannot cap streamed connections, so a configured cap + * marker rejects enabled response streaming ("stream" => true). Accepted + * transfers are buffered and hold at most one connection per in-flight + * call, but overlapping buffered calls are not collectively limited. + * + * @param array{max_host_connections?: mixed, max_total_connections?: mixed, transport_sharing?: mixed} $options Array of options to use with the handler + */ + public function __construct(array $options = []) + { + foreach ($options as $name => $_) { + if (!isset(self::KNOWN_CONSTRUCTOR_OPTIONS[$name])) { + throw new InvalidArgumentException(\sprintf('Invalid StreamHandler constructor option "%s".', Psr7\DiagnosticValue::escape((string) $name))); + } + } + + $this->transportSharingMode = CurlShareHandleState::normalizeMode( + $options['transport_sharing'] ?? null, + 'transport_sharing' + ); + + foreach (['max_host_connections', 'max_total_connections'] as $capOption) { + $value = $options[$capOption] ?? null; + if ($value === null) { + continue; + } + + if (!\is_int($value) || $value < 1) { + throw new InvalidArgumentException(\sprintf('%s must be a positive integer.', $capOption)); + } + + $this->connectionCapsConfigured = true; + } + } /** * Sends an HTTP request. * * @param RequestInterface $request Request to send. * @param array $options Request transfer options. + * + * @return PromiseInterface */ - public function __invoke(RequestInterface $request, array $options): PromiseInterface - { + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { + $this->onStatsException = null; + // Sleep if there is a delay specified. if (isset($options['delay'])) { - \usleep($options['delay'] * 1000); + Clock::sleep((int) ($options['delay'] * 1000)); } - $startTime = isset($options['on_stats']) ? Utils::currentTime() : null; + $multiplex = $options['multiplex'] ?? null; + + // Multiplexing::NONE is trivially satisfied: the stream handler sends + // one HTTP/1.x request per connection and never multiplexes. + if (null !== $multiplex && !\in_array($multiplex, [Multiplexing::NONE, Multiplexing::EAGER, Multiplexing::WAIT, Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new InvalidArgumentException(\sprintf( + 'The "multiplex" option must be null or a GuzzleHttp\\Multiplexing::* constant; received %s.', + \get_debug_type($multiplex) + )); + } + + if (\in_array($multiplex, [Multiplexing::REQUIRE_EAGER, Multiplexing::REQUIRE_WAIT], true)) { + throw new RequestException('The stream handler cannot guarantee a multiplexed protocol; required multiplexing needs a cURL handler.', $request); + } + + $protocolVersion = $request->getProtocolVersion(); + + if ('' === $protocolVersion) { + throw new RequestException('HTTP protocol version must not be empty.', $request); + } + + if (1 !== \preg_match('/^\d+(?:\.\d+)?$/D', $protocolVersion)) { + throw new RequestException('HTTP protocol version must be a valid HTTP version number.', $request); + } + + if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) { + throw new RequestException(sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request); + } + + if (isset($options['on_stats']) && !\is_callable($options['on_stats'])) { + throw new InvalidArgumentException('on_stats must be callable'); + } + + $startTime = isset($options['on_stats']) ? Clock::now() : null; + + self::rejectUnsupportedRequestOptions($request, $options); + $this->rejectStreamingWithConnectionCaps($options); + $this->assertTransportSharingSupported(); + + // The stream wrapper sends HEAD request bodies, unlike cURL's NOBODY + // path, so validate their framing normally. + $framing = RequestFraming::analyze($request->withoutHeader('Expect')); + $request = $framing->request; try { - // Does not support the expect header. - $request = $request->withoutHeader('Expect'); - - // Append a content-length header if body size is zero to match - // cURL's behavior. - if (0 === $request->getBody()->getSize()) { - $request = $request->withHeader('Content-Length', '0'); + self::assertRequestUriSupported($request, $options); + $body = $framing->materialize(); + if ($framing->contentLength === null && ($body !== '' || \in_array($request->getMethod(), ['PUT', 'POST'], true))) { + $request = $request->withHeader('Content-Length', (string) \strlen($body)); } + $deadline = null; + $stream = $this->createStream($request, $options, $body, $deadline); + $headers = \stream_get_meta_data($stream)['wrapper_data']; + return $this->createResponse( $request, $options, - $this->createStream($request, $options), - $startTime + $stream, + \is_array($headers) ? $headers : [], + $startTime, + $deadline ); } catch (\InvalidArgumentException $e) { throw $e; } catch (\Exception $e) { - // Determine if the error was a networking error. - $message = $e->getMessage(); - // This list can probably get more comprehensive. - if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed - || false !== \strpos($message, 'Connection refused') - || false !== \strpos($message, "couldn't connect to host") // error on HHVM - || false !== \strpos($message, "connection attempt failed") - ) { - $e = new ConnectException($e->getMessage(), $request, $e); - } else { - $e = RequestException::wrapException($request, $e); + if ($this->isOnStatsException($e)) { + throw $e; + } + + if (!$e instanceof TransferException) { + $message = $e->getMessage(); + $e = self::createStreamFailureException($message, $request, $e, []); } $this->invokeStats($options, $request, $startTime, null, $e); + /** @var PromiseInterface */ return P\Create::rejectionFor($e); } } + private function isOnStatsException(\Throwable $e): bool + { + if ($this->onStatsException !== $e) { + return false; + } + + $this->onStatsException = null; + + return true; + } + + private static function isConnectTimeoutError(string $message): bool + { + foreach (self::CONNECT_TIMEOUT_ERRORS as $timeoutError) { + if (Psr7\Utils::caselessContains($message, $timeoutError)) { + return true; + } + } + + return false; + } + + private static function isConnectionError(string $message): bool + { + foreach (self::CONNECTION_ERRORS as $connectionError) { + if (Psr7\Utils::caselessContains($message, $connectionError)) { + return true; + } + } + + return false; + } + + private static function isSendError(string $message): bool + { + // A failed write ("Send of N bytes failed ...") implies an established connection. + return Psr7\Utils::caselessContains($message, 'bytes failed with errno='); + } + + private static function isNetworkError(string $message): bool + { + foreach (self::NETWORK_ERRORS as $networkError) { + if (Psr7\Utils::caselessContains($message, $networkError)) { + return true; + } + } + + return false; + } + + /** + * @param string[] $streamErrorCodes + */ + private static function createStreamFailureException( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + \Exception $previous, + array $streamErrorCodes + ): TransferException { + if (self::isSendError($message) || self::hasStreamErrorCode($streamErrorCodes, self::STRUCTURED_NETWORK_ERROR_CODES)) { + return self::isConnectTimeoutError($message) + ? new NetworkTimeoutException($message, $request, $previous) + : new NetworkException($message, $request, $previous); + } + + if (self::isConnectTimeoutError($message)) { + return new ConnectTimeoutException($message, $request, $previous); + } + + if (self::hasStreamErrorCode($streamErrorCodes, self::STRUCTURED_CONNECTION_ERROR_CODES) || self::isConnectionError($message)) { + return new ConnectException($message, $request, $previous); + } + + if (self::isNetworkError($message)) { + return new NetworkException($message, $request, $previous); + } + + return new RequestException($message, $request, 0, $previous); + } + + /** + * @param string[] $streamErrorCodes + * @param string[] $codes + */ + private static function hasStreamErrorCode(array $streamErrorCodes, array $codes): bool + { + foreach ($streamErrorCodes as $streamErrorCode) { + foreach ($codes as $code) { + if ($streamErrorCode === $code) { + return true; + } + } + } + + return false; + } + private function invokeStats( + #[\SensitiveParameter] array $options, + #[\SensitiveParameter] RequestInterface $request, ?float $startTime, - ResponseInterface $response = null, - \Throwable $error = null + #[\SensitiveParameter] + ?ResponseInterface $response = null, + #[\SensitiveParameter] + ?\Throwable $error = null ): void { if (isset($options['on_stats'])) { - $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); - ($options['on_stats'])($stats); + $stats = new TransferStats($request, $response, Clock::now() - $startTime, $error, []); + try { + ($options['on_stats'])($stats); + } catch (\Throwable $e) { + $this->onStatsException = $e; + + throw $e; + } } } /** * @param resource $stream + * + * @return PromiseInterface */ - private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface - { - $hdrs = $this->lastHeaders; - $this->lastHeaders = []; - + private function createResponse( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + $stream, + #[\SensitiveParameter] + array $hdrs, + ?float $startTime, + ?float $deadline + ): PromiseInterface { try { [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); - } catch (\Exception $e) { - return P\Create::rejectionFor( - new RequestException('An error was encountered while creating the response', $request, null, $e) - ); + } catch (\Throwable $e) { + return $this->rejectResponseCreation($options, $request, $startTime, $e); } - [$stream, $headers] = $this->checkDecode($options, $headers, $stream); - $stream = Psr7\Utils::streamFor($stream); - $sink = $stream; - - if (\strcasecmp('HEAD', $request->getMethod())) { - $sink = $this->createSink($stream, $options); + $canHaveBody = HeaderProcessor::responseCanHaveBody($request->getMethod(), $status); + $framingFailure = null; + $declaredLength = null; + $chunked = false; + try { + $declaredLength = HeaderProcessor::validateResponseFraming($request->getMethod(), $status, $headers); + $chunked = $canHaveBody && HeaderProcessor::parseTransferEncoding($headers); + if (empty($options['stream'])) { + HeaderProcessor::assertContentLengthWithinPlatformLimit($declaredLength); + } + } catch (\RuntimeException $e) { + $framingFailure = $e; } + if ($chunked) { + HeaderProcessor::removeHeader('Transfer-Encoding', $headers); + } elseif (!$canHaveBody) { + $headers = self::removeChunkedTransferCoding($headers); + } + + $streamFactory = self::requireStreamFactory($options[RequestOptions::STREAM_FACTORY] ?? new Psr7\HttpFactory()); + $responseFactory = self::requireResponseFactory($options[RequestOptions::RESPONSE_FACTORY] ?? new Psr7\HttpFactory()); + + // Wrap the transport resource with the configured stream factory, and + // decorate buffered transfers with the deadline source before framing + // and optional content decoding layer on top, so every read that pulls + // from the transport observes the deadline. + $resource = $stream; + $stream = $streamFactory->createStreamFromResource($stream); + if ($framingFailure === null && $deadline !== null && empty($options['stream'])) { + $stream = self::createDeadlineSource($stream, $resource, $deadline, $options); + } + if ($chunked || !empty($options['stream'])) { + $stream = new TransportStream($stream); + } + if ($chunked) { + $stream = new ChunkedBodyStream($stream); + } elseif (!empty($options['stream'])) { + $length = HeaderProcessor::contentLengthToInt($declaredLength); + if ($length !== null) { + $stream = new DeclaredLengthStream($stream, $length); + } + } + + $encodedBody = null; + if ($framingFailure === null) { + [$stream, $headers, $encodedBody] = self::checkDecode($options, $headers, $stream, $declaredLength); + } + + $sink = $framingFailure !== null || !$canHaveBody + ? $streamFactory->createStream('') + : $this->createSink($stream, $options); + try { - $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); - } catch (\Exception $e) { - return P\Create::rejectionFor( - new RequestException('An error was encountered while creating the response', $request, null, $e) - ); + $response = $responseFactory->createResponse($status, $reason ?? '')->withProtocolVersion($ver); + foreach ($headers as $name => $value) { + $response = $response->withAddedHeader((string) $name, $value); + } + $response = $response->withBody($sink); + } catch (\Throwable $e) { + return $this->rejectResponseCreation($options, $request, $startTime, $e); + } + + if ($framingFailure !== null) { + try { + $stream->close(); + } catch (\Exception $e) { + // Best-effort release; a failing transport close must not + // mask the framing rejection. + } + + $reason = $framingFailure instanceof \OverflowException + ? new ResponseException($framingFailure->getMessage(), $request, $response, $framingFailure) + : new ResponseTransferException($framingFailure->getMessage(), $request, $response, $framingFailure); + $this->invokeStats($options, $request, $startTime, $response, $reason); + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); + } + + // The header phase inside fopen() can only bound the time between + // packets, so a header block that trickled in past the deadline is + // rejected here, once it is complete. The transport is closed so a + // streamed response cannot hold the connection open through the + // rejection. + if ($deadline !== null && Clock::now() >= $deadline) { + try { + $stream->close(); + } catch (\Exception $e) { + // Best-effort release; a failing transport close must not + // mask the timeout rejection. + } + + $reason = new ResponseTimeoutException('Timed out while receiving the response headers', $request, $response); + $this->invokeStats($options, $request, $startTime, $response, $reason); + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); } if (isset($options['on_headers'])) { try { - $options['on_headers']($response); - } catch (\Exception $e) { - return P\Create::rejectionFor( - new RequestException('An error was encountered during the on_headers event', $request, $response, $e) - ); + $options['on_headers']($response, $request); + } catch (\Throwable $e) { + $reason = new ResponseException('An error was encountered during the on_headers event', $request, $response, $e); + $this->invokeStats($options, $request, $startTime, $response, $reason); + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); } } - // Do not drain when the request is a HEAD request because they have - // no body. - if ($sink !== $stream) { - $this->drain($stream, $sink, $response->getHeaderLine('Content-Length')); + if (!$canHaveBody) { + // RFC 9110 / RFC 9112 section 6.3: octets after the header section + // of a HEAD, 1xx, 204, 304, or CONNECT-2xx response are not part + // of this response's body. Never read them, and release the + // transport as soon as the headers are complete. + try { + $stream->close(); + } catch (\Exception $e) { + // Best-effort release; a failing transport close must not fail + // a fully received no-content response. + } + } elseif ($sink !== $stream) { + try { + $this->drain($request, $response, $stream, $sink, $declaredLength, $encodedBody); + } catch (ResponseException $e) { + $this->invokeStats($options, $request, $startTime, $response, $e); + + /** @var PromiseInterface */ + return P\Create::rejectionFor($e); + } } $this->invokeStats($options, $request, $startTime, $response, null); - return new FulfilledPromise($response); + /** @var PromiseInterface */ + return P\Create::promiseFor($response); } - private function createSink(StreamInterface $stream, array $options): StreamInterface - { + /** + * @return PromiseInterface + */ + private function rejectResponseCreation( + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + RequestInterface $request, + ?float $startTime, + #[\SensitiveParameter] + \Throwable $previous + ): PromiseInterface { + $reason = new RequestException( + 'An error was encountered while creating the response', + $request, + 0, + $previous + ); + + $this->invokeStats($options, $request, $startTime, null, $reason); + + /** @var PromiseInterface */ + return P\Create::rejectionFor($reason); + } + + private function createSink( + StreamInterface $stream, + #[\SensitiveParameter] + array $options + ): StreamInterface { if (!empty($options['stream'])) { return $stream; } - $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + $streamFactory = self::requireStreamFactory($options[RequestOptions::STREAM_FACTORY] ?? new Psr7\HttpFactory()); + $hasSink = isset($options['sink']); + $sink = $hasSink ? $options['sink'] : Psr7\Utils::tryFopen('php://temp', 'r+'); - return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + if ($hasSink && \is_resource($sink)) { + return self::streamForResourceSink(Psr7\Utils::streamFor($sink)); + } + + if (\is_string($sink)) { + return new Psr7\LazyOpenStream($sink, 'w+'); + } + + if (!\is_resource($sink)) { + return Psr7\Utils::streamFor($sink); + } + + return $streamFactory->createStreamFromResource($sink); } /** - * @param resource $stream + * Decorates a caller-owned sink stream so that closing the response body + * detaches Guzzle's wrapper without closing the original PHP resource. */ - private function checkDecode(array $options, array $headers, $stream): array + private static function streamForResourceSink(StreamInterface $stream): StreamInterface { + return Psr7\FnStream::decorate($stream, [ + 'close' => static function () use ($stream): void { + $stream->detach(); + }, + ]); + } + + /** + * @param mixed $factory + */ + private static function requireStreamFactory($factory): StreamFactoryInterface + { + if (!$factory instanceof StreamFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::STREAM_FACTORY, + StreamFactoryInterface::class + )); + } + + return $factory; + } + + /** + * @param mixed $factory + */ + private static function requireResponseFactory($factory): ResponseFactoryInterface + { + if (!$factory instanceof ResponseFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::RESPONSE_FACTORY, + ResponseFactoryInterface::class + )); + } + + return $factory; + } + + /** + * Removes Transfer-Encoding values that start with chunked from a response + * that cannot have a body, as PHP's HTTP wrapper did. + * + * @param array $headers + * + * @return array + */ + private static function removeChunkedTransferCoding(array $headers): array + { + foreach ($headers as $name => $values) { + if (!Psr7\Utils::caselessEquals((string) $name, 'Transfer-Encoding')) { + continue; + } + + $remaining = []; + foreach ($values as $value) { + if (!Psr7\Utils::caselessEquals(\substr($value, 0, 7), 'chunked')) { + $remaining[] = $value; + } + } + + if ($remaining === []) { + unset($headers[$name]); + } else { + $headers[$name] = $remaining; + } + } + + return $headers; + } + + /** + * @return array{0: StreamInterface, 1: array, 2: ?EncodedBodyStream} + */ + private static function checkDecode( + #[\SensitiveParameter] + array $options, + array $headers, + StreamInterface $stream, + ?string $declaredLength + ): array { + $encodedBody = null; + // Automatically decode responses when instructed. - if (!empty($options['decode_content'])) { + if (isset($options['decode_content']) && $options['decode_content'] !== false) { $normalizedKeys = Utils::normalizeHeaderKeys($headers); if (isset($normalizedKeys['content-encoding'])) { $encoding = $headers[$normalizedKeys['content-encoding']]; if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { - $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream)); + if (empty($options['stream']) && $declaredLength !== null && $declaredLength !== '0') { + $encodedBody = new EncodedBodyStream($stream, $declaredLength); + $stream = $encodedBody; + } + + $stream = new InflateBodyStream($stream, $encoding[0] === 'deflate'); $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; // Remove content-encoding header unset($headers[$normalizedKeys['content-encoding']]); - // Fix content-length header - if (isset($normalizedKeys['content-length'])) { - $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; - $length = (int) $stream->getSize(); - if ($length === 0) { - unset($headers[$normalizedKeys['content-length']]); - } else { - $headers[$normalizedKeys['content-length']] = [$length]; - } + // The decoded length cannot be known without inflating the + // stream, so keep the original length for inspection and + // drop the now-unknown Content-Length header. + $encodedContentLength = HeaderProcessor::removeHeader('Content-Length', $headers); + if ($encodedContentLength !== []) { + $headers['x-encoded-content-length'] = $encodedContentLength; } } } } - return [$stream, $headers]; + return [$stream, $headers, $encodedBody]; } /** * Drains the source stream into the "sink" client option. * - * @param string $contentLength Header specifying the amount of - * data to read. - * * @throws \RuntimeException when the sink option is invalid. */ - private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface - { - // If a content-length header is provided, then stop reading once - // that number of bytes has been read. This can prevent infinitely - // reading from a stream when dealing with servers that do not honor - // Connection: Close headers. - Psr7\Utils::copyToStream( - $source, - $sink, - (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 - ); + private function drain( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + ResponseInterface $response, + StreamInterface $source, + StreamInterface $sink, + ?string $declaredResponseBodyLength, + ?EncodedBodyStream $encodedBody = null + ): StreamInterface { + try { + // Buffered paths reject unrepresentable lengths before draining. + $declaredLength = HeaderProcessor::contentLengthToInt($declaredResponseBodyLength); + $copyLimit = $encodedBody === null ? $declaredLength ?? -1 : -1; - $sink->seek(0); - $source->close(); + try { + $target = $this->createResponseSink($request, $response, $sink); + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not + // honor Connection: Close headers. + $copied = Psr7\Utils::copyToStream($source, $target, $copyLimit); + } catch (ResponseException $e) { + throw $e; + } catch (TimeoutException $e) { + throw new ResponseTimeoutException( + 'Timed out while transferring the response body', + $request, + $response, + $e + ); + } catch (\OverflowException $e) { + throw new ResponseException($e->getMessage(), $request, $response, $e); + } catch (\Exception $e) { + // Any other response-body transfer failure surfaces as a + // ResponseTransferException carrying the response. + throw new ResponseTransferException( + $e->getMessage() !== '' ? $e->getMessage() : 'Failed while transferring the response body', + $request, + $response, + $e + ); + } - return $sink; + $receivedLength = $encodedBody !== null ? $encodedBody->getBytesRead() : $copied; + if ($declaredLength !== null && $receivedLength < $declaredLength) { + throw new ResponseTransferException( + 'Response body ended before the declared Content-Length was reached', + $request, + $response + ); + } + + try { + if ($sink->isSeekable()) { + $sink->rewind(); + } + } catch (\Exception $e) { + throw new ResponseException( + $e->getMessage() !== '' ? $e->getMessage() : 'Failed to rewind the response body', + $request, + $response, + $e + ); + } + + return $sink; + } finally { + try { + $source->close(); + } catch (\Exception $e) { + // Best-effort cleanup after the response body has been received. + } + } + } + + private function createResponseSink( + RequestInterface $request, + ResponseInterface $response, + StreamInterface $sink + ): StreamInterface { + return Psr7\FnStream::decorate($sink, [ + 'close' => static function (): void { + }, + 'write' => static function (string $data) use ($request, $response, $sink): int { + try { + $written = $sink->write($data); + } catch (TimeoutException $e) { + throw new ResponseException( + 'Timed out while writing the response body', + $request, + $response, + $e + ); + } catch (\Exception $e) { + throw new ResponseException( + $e->getMessage() !== '' ? $e->getMessage() : 'Failed to write the response body', + $request, + $response, + $e + ); + } + + if ($written <= 0) { + throw new ResponseException('Unable to write to stream', $request, $response); + } + + return $written; + }, + 'getMetadata' => static function (?string $key = null) use ($sink) { + // Force timed_out to false so Utils::writeAll() can't reclassify a sink-write + // failure as a transport timeout. Sink write failures are ResponseException; + // source-read timeouts are ResponseTimeoutException. + if ($key === 'timed_out') { + return false; + } + + return $sink->getMetadata($key); + }, + ]); + } + + /** + * Decorates the transport stream so each buffered-body read observes the + * remaining wall-clock budget of the "timeout" request option, bounded by + * the "read_timeout" idle timeout when that is shorter. The transport is + * switched to non-blocking mode because a blocking read waits for data up + * to the transport's own timeout, which would keep the deadline from being + * enforced while the server stalls. + * + * @param resource $resource + */ + private static function createDeadlineSource( + StreamInterface $stream, + $resource, + float $deadline, + #[\SensitiveParameter] + array $options + ): StreamInterface { + $idleTimeout = isset($options['read_timeout']) + ? Timeout::toMilliseconds($options['read_timeout'], 'read_timeout') + : self::DEFAULT_IDLE_TIMEOUT_MS; + + \stream_set_blocking($resource, false); + + return new DeadlineSourceStream($stream, $deadline, $idleTimeout > 0 ? $idleTimeout / 1000 : null); } /** * Create a resource and check to ensure it was created successfully * - * @param callable $callback Callable that returns stream resource + * @param callable(): (resource|false) $callback Callable that returns a stream resource, or false when resource creation fails. * * @return resource * - * @throws \RuntimeException on error + * @throws \RuntimeException when the callback returns false or resource creation emits an error. */ - private function createResource(callable $callback) - { + private function createResource( + callable $callback, + #[\SensitiveParameter] + ?UriInterface $diagnosticUri = null + ) { $errors = []; - \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + \set_error_handler(static function (int $_, string $msg, string $file, int $line) use (&$errors): bool { $errors[] = [ 'message' => $msg, - 'file' => $file, - 'line' => $line + 'file' => $file, + 'line' => $line, ]; + return true; }); @@ -244,13 +903,22 @@ class StreamHandler } if (!$resource) { - $message = 'Error creating resource: '; + $details = []; foreach ($errors as $err) { foreach ($err as $key => $value) { - $message .= "[$key] $value" . \PHP_EOL; + $rendered = $key === 'message' && $diagnosticUri !== null + ? UriDiagnostic::redactInMessage((string) $value, $diagnosticUri) + : Psr7\DiagnosticValue::escape((string) $value); + $details[] = \sprintf('[%s] %s', $key, $rendered); } } - throw new \RuntimeException(\trim($message)); + + $message = 'Error creating resource'; + if ($details !== []) { + $message .= ': '.\implode('; ', $details); + } + + throw new \RuntimeException($message); } return $resource; @@ -259,20 +927,21 @@ class StreamHandler /** * @return resource */ - private function createStream(RequestInterface $request, array $options) - { - static $methods; - if (!$methods) { - $methods = \array_flip(\get_class_methods(__CLASS__)); - } - - if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) { - throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request); - } + private function createStream( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + string $body, + ?float &$deadline + ) { + // Report a stream-open failure against the request passed here, not the + // Connection: close clone built for the wire. + $callerRequest = $request; // HTTP/1.1 streams using the PHP stream wrapper require a // Connection: close header - if ($request->getProtocolVersion() == '1.1' + if ($request->getProtocolVersion() === '1.1' && !$request->hasHeader('Connection') ) { $request = $request->withHeader('Connection', 'close'); @@ -284,33 +953,63 @@ class StreamHandler } $params = []; - $context = $this->getDefaultContext($request); + $context = $this->getDefaultContext($request, $body); + $customSslContext = []; if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { - throw new \InvalidArgumentException('on_headers must be callable'); + throw new InvalidArgumentException('on_headers must be callable'); } - if (!empty($options)) { - foreach ($options as $key => $value) { - $method = "add_{$key}"; - if (isset($methods[$method])) { - $this->{$method}($request, $context, $value, $params); - } - } - } + $idleTimeout = isset($options['read_timeout']) + ? Timeout::toMilliseconds($options['read_timeout'], 'read_timeout') + : self::DEFAULT_IDLE_TIMEOUT_MS; + + $timeout = isset($options['timeout']) + ? Timeout::toMilliseconds($options['timeout'], 'timeout') + : 0; + + self::assertTlsVersionRangeForOptions($request, $options); + + $this->applyHandlerOptions($request, $context, $options, $params); + + // Resolve the proxy unconditionally (option first, then environment), + // so an env-configured proxy applies even when no proxy option is set. + $this->applyProxy($request, $context, $options['proxy'] ?? null); if (isset($options['stream_context'])) { - if (!\is_array($options['stream_context'])) { - throw new \InvalidArgumentException('stream_context must be an array'); + $streamContext = $options['stream_context']; + if (!\is_array($streamContext)) { + throw new InvalidArgumentException('stream_context must be an array'); + } + self::rejectConflictingStreamContextOptions($streamContext); + self::rejectUnsupportedStreamContextOptions($streamContext); + $context = \array_replace_recursive($context, $streamContext); + + $sslContext = $streamContext['ssl'] ?? null; + if (\is_array($sslContext)) { + $customSslContext = $sslContext; } - $context = \array_replace_recursive($context, $options['stream_context']); } - // Microsoft NTLM authentication only supported with curl handler - if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) { - throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + $this->addDefaultTlsMinimum($request, $context); + + $commitTlsSessions = $this->applyTlsSessionResumption($request, $context, $customSslContext); + + // The context timeout governs connecting and the header-phase packet + // gaps: the idle timeout, tightened to the deadline when that is + // lower; -1 disables it so default_socket_timeout is never consulted. + if ($timeout > 0 && ($idleTimeout <= 0 || $timeout < $idleTimeout)) { + $context['http']['timeout'] = $timeout / 1000; + } elseif ($idleTimeout > 0) { + $context['http']['timeout'] = $idleTimeout / 1000; + } else { + $context['http']['timeout'] = -1; } + $streamErrorCodes = []; + $captureStreamErrors = true; + $this->addStructuredStreamErrorHandler($context, $streamErrorCodes, $captureStreamErrors); + $uri = $this->resolveHost($request, $options); $contextResource = $this->createResource( @@ -319,55 +1018,413 @@ class StreamHandler } ); - return $this->createResource( - function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { - $resource = @\fopen((string) $uri, 'r', false, $contextResource); - $this->lastHeaders = $http_response_header ?? []; + try { + $resource = $this->createResource(function () use ($uri, $contextResource, $idleTimeout, $timeout, &$deadline) { + $deadline = $timeout > 0 ? Clock::now() + $timeout / 1000 : null; - if (false === $resource) { - throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + // Blank the from ini setting for the transfer so ambient + // configuration cannot leak into a From header; the wrapper + // cannot omit the header, so a configured ini sends it empty. + $iniFrom = \function_exists('ini_set') ? \ini_set('from', '') : false; + try { + $resource = @\fopen((string) $uri, 'r', false, $contextResource); + } finally { + if ($iniFrom !== false) { + \ini_set('from', $iniFrom); + } } - if (isset($options['read_timeout'])) { - $readTimeout = $options['read_timeout']; - $sec = (int) $readTimeout; - $usec = ($readTimeout - $sec) * 100000; + if (false === $resource) { + return false; + } + + // Arm reads with the idle timeout, replacing the context + // value the deadline may have tightened; -1 disables it. + if ($idleTimeout > 0) { + $sec = \intdiv($idleTimeout, 1000); + $usec = ($idleTimeout % 1000) * 1000; \stream_set_timeout($resource, $sec, $usec); + } else { + \stream_set_timeout($resource, -1); } return $resource; - } - ); + }, $uri); + } catch (TransferException $e) { + // Notification callbacks run during fopen(); an exception a + // callback throws is already a fully-formed transfer failure for + // its own request, so it passes through unchanged instead of + // being reclassified against this request. + throw $e; + } catch (\RuntimeException $e) { + throw self::createStreamFailureException($e->getMessage(), $callerRequest, $e, $streamErrorCodes); + } finally { + $captureStreamErrors = false; + } + + if ($commitTlsSessions !== null) { + $commitTlsSessions(); + } + + return $resource; } - private function resolveHost(RequestInterface $request, array $options): UriInterface + /** + * @param string[] $streamErrorCodes + */ + private function addStructuredStreamErrorHandler( + #[\SensitiveParameter] + array &$context, + array &$streamErrorCodes, + bool &$captureStreamErrors + ): void { + if (!self::supportsStructuredStreamErrors()) { + return; + } + + if (!isset($context['stream']) || !\is_array($context['stream'])) { + $context['stream'] = []; + } + + $context['stream']['error_mode'] = \StreamErrorMode::Error; + $context['stream']['error_store'] = \StreamErrorStore::None; + /** @param \StreamError[] $errors */ + $context['stream']['error_handler'] = static function ( + #[\SensitiveParameter] + array $errors + ) use (&$streamErrorCodes, &$captureStreamErrors): void { + if (!$captureStreamErrors) { + return; + } + + foreach ($errors as $error) { + $name = $error->code->name; + if (!\in_array($name, $streamErrorCodes, true)) { + $streamErrorCodes[] = $name; + } + } + }; + } + + private static function supportsStructuredStreamErrors(): bool { + // Any PHP 8.6 build qualifies, including pre-release and nightly + // builds; the class check keeps unstable builds that do not carry + // the final API fail-closed. + return \PHP_VERSION_ID >= 80600 && \class_exists(\StreamError::class, false); + } + + private static function assertRequestUriSupported( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): void { + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + if ($scheme === '') { + throw new RequestException( + 'URI must include a scheme and host. Use an absolute URI, a network-path reference starting with //, or configure a base_uri.', + $request + ); + } + + if (!\in_array($scheme, ['http', 'https'], true)) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", Psr7\DiagnosticValue::escape($scheme)), $request); + } + + $protocols = Utils::normalizeProtocols($options['protocols'] ?? ['http', 'https']); + if (!\in_array($scheme, $protocols, true)) { + throw new RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', Psr7\DiagnosticValue::escape($scheme)), $request); + } + + if ($uri->getHost() === '') { + throw new RequestException( + 'URI must include a scheme and host. Use an absolute URI, a network-path reference starting with //, or configure a base_uri.', + $request + ); + } + + HostValidator::assertRequestHost($request); + } + + private function applyHandlerOptions( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$context, + #[\SensitiveParameter] + array $options, + array &$params + ): void { + foreach ($options as $key => $value) { + if ($key === 'crypto_method') { + $this->applyCryptoMethodOption($context, $value); + } elseif ($key === 'crypto_method_max') { + $this->applyCryptoMethodMaxOption($context, $value); + } elseif ($key === 'verify') { + $this->applyVerifyOption($context, $value); + } elseif ($key === 'cert') { + $this->applyCertOption($context, $value); + } elseif ($key === 'cert_type') { + $this->applyCertTypeOption($value); + } elseif ($key === 'ssl_key') { + $this->applySslKeyOption($context, $value); + } elseif ($key === 'ssl_key_type') { + $this->applySslKeyTypeOption($value); + } elseif ($key === 'progress') { + $this->applyProgressOption($value, $params); + } elseif ($key === 'debug') { + $this->applyDebugOption($request, $value, $params); + } + } + } + + private function resolveHost( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): UriInterface { $uri = $request->getUri(); - if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) { + $host = $uri->getHost(); + + // Fold a numeric IPv4 spelling to the dotted quad libcurl connects + // to, rather than leaving it to the platform resolver: macOS reads + // the zero-padded 0177 as decimal 177 where glibc, musl and FreeBSD + // read octal 127. The Host header is serialized from the request and + // stays as written; the TLS peer name follows the same fold. + $canonicalHost = self::canonicalConnectionHost($host); + if ($canonicalHost !== $host) { + $uri = $uri->withHost($canonicalHost); + $host = $canonicalHost; + } + + $hostForIpCheck = \str_starts_with($host, '[') && \str_ends_with($host, ']') + ? \substr($host, 1, -1) + : $host; + if (isset($options['force_ip_resolve']) && !\filter_var($hostForIpCheck, \FILTER_VALIDATE_IP)) { if ('v4' === $options['force_ip_resolve']) { $records = \dns_get_record($uri->getHost(), \DNS_A); if (false === $records || !isset($records[0]['ip'])) { - throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", Psr7\DiagnosticValue::escape($uri->getHost())), $request); } + return $uri->withHost($records[0]['ip']); } if ('v6' === $options['force_ip_resolve']) { $records = \dns_get_record($uri->getHost(), \DNS_AAAA); if (false === $records || !isset($records[0]['ipv6'])) { - throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", Psr7\DiagnosticValue::escape($uri->getHost())), $request); } - return $uri->withHost('[' . $records[0]['ipv6'] . ']'); + + return $uri->withHost('['.$records[0]['ipv6'].']'); } } return $uri; } - private function getDefaultContext(RequestInterface $request): array + /** + * Returns a numeric IPv4 spelling folded to the dotted quad libcurl's + * ipv4_normalize() produces, and every other host unchanged. + */ + private static function canonicalConnectionHost(string $host): string { + $binary = HostIdentity::numericIpv4ToBinary($host); + if ($binary === null) { + return $host; + } + + return (string) \inet_ntop($binary); + } + + private function addDefaultTlsMinimum(RequestInterface $request, array &$context): void + { + if ('https' !== $request->getUri()->getScheme() || !isset($context['ssl']) || !\is_array($context['ssl'])) { + return; + } + + if ( + \array_key_exists('crypto_method', $context['ssl']) + || \array_key_exists('min_proto_version', $context['ssl']) + ) { + return; + } + + $context['ssl']['min_proto_version'] = \STREAM_CRYPTO_PROTO_TLSv1_2; + } + + /** + * Wires PHP 8.6+ TLS session resumption into the SSL context. Preferred + * sharing modes fall back to no sharing when a request cannot safely use + * Guzzle-managed sessions; HANDLER_REQUIRE fails loudly instead. + * + * New sessions are held temporarily and committed only after the HTTPS + * stream opens successfully. If PHP rejects peer verification or another + * stream-open step fails, any session reported during that attempt is + * discarded. + * + * @param array $customSslContext User-supplied stream_context['ssl'] + * values. + */ + private function applyTlsSessionResumption( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$context, + #[\SensitiveParameter] + array $customSslContext = [] + ): ?\Closure { + if (!$this->transportSharingRequested()) { + return null; + } + + $uri = $request->getUri(); + + if ('https' !== $uri->getScheme()) { + $this->failRequiredTlsSharingForRequest($request, 'handler-lifetime TLS session sharing only applies to HTTPS requests.'); + + return null; + } + + $host = $uri->getHost(); + + // PHP opens a proxy transport with the full stream context, so a TLS + // proxy handshake would consume origin-keyed session state; only a + // tcp:// proxy keeps TLS on the origin leg alone. + $proxy = $context['http']['proxy'] ?? null; + if (\is_string($proxy) && ProxyOptions::proxyScheme($proxy) !== 'tcp') { + $this->failRequiredTlsSharingForRequest($request, 'the proxy uses a TLS stream transport, which would mix proxy and origin TLS session state.'); + + return null; + } + + if (!isset($context['ssl']) || !\is_array($context['ssl'])) { + $this->failRequiredTlsSharingForConfiguration('the final stream SSL context is not an array.'); + + return null; + } + + $unsupported = StreamTlsSessionCache::unsupportedContextReason($context['ssl'], $customSslContext); + if ($unsupported !== null) { + $this->failRequiredTlsSharingForConfiguration($unsupported); + + return null; + } + + $cache = $this->sessionCache(); + if ($cache === null) { + $this->failRequiredTlsSharingForConfiguration('PHP 8.6+ with the OpenSSL session API is required.'); + + return null; + } + + $key = StreamTlsSessionCache::peerKey(self::canonicalConnectionHost($host), $uri->getPort() ?? 443, $context['ssl']); + $credentials = StreamTlsSessionCache::credentialFingerprint($context['ssl']); + + $session = $cache->find($key, $credentials); + if ($session !== null) { + $context['ssl']['session_data'] = $session; + } + + // Sessions captured before the stream opens are staged so handshakes + // that fail PHP's peer verification policy are never cached. + $accepted = false; + $staged = []; + + $context['ssl']['session_new_cb'] = static function ( + #[\SensitiveParameter] + $stream, + #[\SensitiveParameter] + Session $session + ) use ($cache, $key, $credentials, &$accepted, &$staged): void { + if ($accepted) { + $cache->store($key, $credentials, $session); + + return; + } + + // A server can stream session tickets while withholding response + // headers; keep only as many staged sessions as the cache retains. + if (\count($staged) >= StreamTlsSessionCache::MAX_SESSIONS_PER_KEY) { + \array_shift($staged); + } + + $staged[] = $session; + }; + + return static function () use ($cache, $key, $credentials, &$accepted, &$staged): void { + $accepted = true; + + foreach ($staged as $session) { + $cache->store($key, $credentials, $session); + } + + $staged = []; + }; + } + + private function transportSharingRequested(): bool + { + return $this->transportSharingMode !== TransportSharing::NONE + && $this->transportSharingMode !== TransportSharing::PERSISTENT_REQUIRE; + } + + private function transportSharingRequired(): bool + { + return $this->transportSharingMode === TransportSharing::HANDLER_REQUIRE; + } + + private function failRequiredTlsSharingForRequest( + #[\SensitiveParameter] + RequestInterface $request, + string $reason + ): void { + if ($this->transportSharingRequired()) { + throw new RequestException('The "transport_sharing" option requires stream handler TLS session sharing, but '.$reason, $request); + } + } + + private function failRequiredTlsSharingForConfiguration(string $reason): void + { + if ($this->transportSharingRequired()) { + throw new InvalidArgumentException('The "transport_sharing" option requires stream handler TLS session sharing, but '.$reason); + } + } + + /** + * Returns this handler's TLS session cache, or null when the configured + * sharing mode shares nothing or the OpenSSL session API is unavailable. + * Persistent (process-wide) sharing is a cURL-only feature, so + * PERSISTENT_PREFER degrades to this per-handler cache while + * PERSISTENT_REQUIRE is rejected before sharing applies. + */ + private function sessionCache(): ?StreamTlsSessionCache + { + if (!$this->transportSharingRequested() || !StreamTlsSessionCache::isSupported()) { + return null; + } + + return $this->sessionCache ?? ($this->sessionCache = new StreamTlsSessionCache(self::TLS_SESSION_CACHE_MAX_KEYS)); + } + + private function getDefaultContext( + #[\SensitiveParameter] + RequestInterface $request, + string $body + ): array { $headers = ''; foreach ($request->getHeaders() as $name => $value) { + // The first-class Proxy-Authorization field never enters the + // stream context header block: PHP streams have no proxy-only + // header channel, so applyProxy() rejects the field when a proxy + // is selected and it is omitted on direct and bypassed routes. + if (Psr7\Utils::caselessEquals((string) $name, 'Proxy-Authorization')) { + continue; + } + foreach ($value as $val) { $headers .= "$name: $val\r\n"; } @@ -375,20 +1432,26 @@ class StreamHandler $context = [ 'http' => [ - 'method' => $request->getMethod(), - 'header' => $headers, + 'auto_decode' => false, + 'method' => $request->getMethod(), + 'header' => $headers, 'protocol_version' => $request->getProtocolVersion(), - 'ignore_errors' => true, - 'follow_location' => 0, + 'ignore_errors' => true, + 'follow_location' => 0, ], 'ssl' => [ - 'peer_name' => $request->getUri()->getHost(), + 'peer_name' => self::canonicalConnectionHost($request->getUri()->getHost()), ], ]; - $body = (string) $request->getBody(); + // An empty context user_agent stops the HTTP stream wrapper from + // appending a User-Agent header from the user_agent ini setting, so a + // request without the header sends none, like the cURL handlers. + if (!$request->hasHeader('User-Agent')) { + $context['http']['user_agent'] = ''; + } - if (!empty($body)) { + if ('' !== $body) { $context['http']['content'] = $body; // Prevent the HTTP handler from adding a Content-Type header. if (!$request->hasHeader('Content-Type')) { @@ -396,66 +1459,387 @@ class StreamHandler } } - $context['http']['header'] = \rtrim($context['http']['header']); + $context['http']['header'] = \rtrim($context['http']['header'], "\r\n"); return $context; } + private function assertTransportSharingSupported(): void + { + // The stream handler cannot pool live connections or share state across + // handler instances; persistent sharing is cURL-only. + if ($this->transportSharingMode === TransportSharing::PERSISTENT_REQUIRE) { + throw new InvalidArgumentException('The "transport_sharing" option requires persistent transport sharing, which is only available through cURL share handles. The stream handler can only share handler-lifetime TLS sessions.'); + } + + // Handler-scoped sharing needs the PHP 8.6+ OpenSSL session API. + if ($this->transportSharingMode === TransportSharing::HANDLER_REQUIRE && !StreamTlsSessionCache::isSupported()) { + throw new InvalidArgumentException('The "transport_sharing" option requires handler-lifetime transport sharing, but the stream handler only supports it through TLS session resumption on PHP 8.6+ with the OpenSSL session API.'); + } + } + + private static function rejectUnsupportedRequestOptions( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): void { + if ( + \array_key_exists('curl', $options) + && $options['curl'] !== null + && $options['curl'] !== [] + ) { + throw new InvalidArgumentException('Passing the "curl" request option to the stream handler is not supported because the stream handler ignores cURL options.'); + } + + if (\array_key_exists('expect', $options) && $options['expect'] !== false && $request->hasHeader('Expect')) { + throw new InvalidArgumentException('Passing the "expect" request option to the stream handler is not supported when it adds an Expect header because the stream handler does not support Expect: 100-Continue.'); + } + + if (isset($options['on_trailers'])) { + throw new InvalidArgumentException('Passing the "on_trailers" request option to the stream handler is not supported because the stream handler cannot observe trailers.'); + } + } + + private function rejectStreamingWithConnectionCaps( + #[\SensitiveParameter] + array $options + ): void { + if ($this->connectionCapsConfigured && !empty($options['stream'])) { + throw new InvalidArgumentException('Enabling the "stream" request option on a stream handler configured with the "max_host_connections" or "max_total_connections" option is not supported because streamed connections cannot be capped.'); + } + } + + private static function rejectConflictingStreamContextOptions( + #[\SensitiveParameter] + array $streamContext + ): void { + $conflictingOptions = self::conflictingStreamContextOptions(); + + foreach ($streamContext as $wrapper => $contextOptions) { + if (!\is_string($wrapper) || !isset($conflictingOptions[$wrapper]) || !\is_array($contextOptions)) { + continue; + } + + foreach ($contextOptions as $option => $_) { + if (!\is_string($option) || !\array_key_exists($option, $conflictingOptions[$wrapper])) { + continue; + } + + $replacement = $conflictingOptions[$wrapper][$option]; + throw new InvalidArgumentException(\sprintf( + 'Passing stream_context.%s.%s in the "stream_context" request option is not supported because it conflicts with Guzzle-managed request handling. Use %s instead.', + Psr7\DiagnosticValue::escape($wrapper), + Psr7\DiagnosticValue::escape($option), + $replacement + )); + } + } + } + + private static function rejectUnsupportedStreamContextOptions( + #[\SensitiveParameter] + array $streamContext + ): void { + $unsupportedOptions = self::unsupportedStreamContextOptions($streamContext); + if ($unsupportedOptions === []) { + return; + } + + throw new InvalidArgumentException(\sprintf( + 'Passing PHP stream context options outside the built-in stream handler allow-list to the "stream_context" request option is not supported. Unsupported option%s: %s.', + \count($unsupportedOptions) === 1 ? '' : 's', + \implode(', ', $unsupportedOptions) + )); + } + + /** + * @return string[] + */ + private static function unsupportedStreamContextOptions(array $streamContext): array + { + $supportedOptions = self::supportedStreamContextOptions(); + $conflictingOptions = self::conflictingStreamContextOptions(); + $unsupportedOptions = []; + + foreach ($streamContext as $wrapper => $contextOptions) { + if (!\is_string($wrapper) || !isset($supportedOptions[$wrapper])) { + if (\is_array($contextOptions)) { + foreach ($contextOptions as $option => $_) { + if (\is_string($wrapper) && \is_string($option) && isset($conflictingOptions[$wrapper]) && \array_key_exists($option, $conflictingOptions[$wrapper])) { + continue; + } + + $unsupportedOptions[] = \sprintf('stream_context.%s.%s', Psr7\DiagnosticValue::escape((string) $wrapper), Psr7\DiagnosticValue::escape((string) $option)); + } + } else { + $unsupportedOptions[] = \sprintf('stream_context.%s', Psr7\DiagnosticValue::escape((string) $wrapper)); + } + + continue; + } + + if (!\is_array($contextOptions)) { + $unsupportedOptions[] = \sprintf('stream_context.%s', Psr7\DiagnosticValue::escape($wrapper)); + + continue; + } + + foreach ($contextOptions as $option => $_) { + if (\is_string($option) && isset($conflictingOptions[$wrapper]) && \array_key_exists($option, $conflictingOptions[$wrapper])) { + continue; + } + + if (!\is_string($option) || !\array_key_exists($option, $supportedOptions[$wrapper])) { + $unsupportedOptions[] = \sprintf('stream_context.%s.%s', Psr7\DiagnosticValue::escape($wrapper), Psr7\DiagnosticValue::escape((string) $option)); + } + } + } + + return $unsupportedOptions; + } + + /** + * @return array> + */ + private static function supportedStreamContextOptions(): array + { + return [ + 'http' => [ + 'request_fulluri' => true, + ], + 'socket' => [ + 'bindto' => true, + 'tcp_nodelay' => true, + ], + 'ssl' => [ + 'SNI_enabled' => true, + 'capture_peer_cert' => true, + 'capture_peer_cert_chain' => true, + 'ciphers' => true, + 'disable_compression' => true, + 'no_ticket' => true, + 'peer_fingerprint' => true, + 'security_level' => true, + 'verify_depth' => true, + ], + ]; + } + + /** + * @return array> + */ + private static function conflictingStreamContextOptions(): array + { + return [ + 'http' => [ + 'auto_decode' => 'Guzzle response framing and decoding', + 'content' => 'the request body', + 'follow_location' => 'the "allow_redirects" request option', + 'header' => 'the request headers', + 'max_redirects' => 'the "allow_redirects" request option', + 'method' => 'the request method', + 'protocol_version' => 'the request protocol version', + 'proxy' => 'the "proxy" request option', + 'timeout' => 'the "timeout" and "read_timeout" request options', + ], + 'ssl' => [ + 'allow_self_signed' => 'the "verify" request option', + 'cafile' => 'the "verify" request option', + 'capath' => 'the "verify" request option', + 'crypto_method' => 'the "crypto_method" request option', + 'local_cert' => 'the "cert" request option', + 'local_pk' => 'the "ssl_key" request option', + 'max_proto_version' => 'the "crypto_method_max" request option', + 'min_proto_version' => 'the "crypto_method" request option', + 'passphrase' => 'the "cert" or "ssl_key" request option', + 'peer_name' => 'the request URI', + 'verify_peer' => 'the "verify" request option', + 'verify_peer_name' => 'the "verify" request option', + ], + 'stream' => [ + 'error_handler' => 'Guzzle stream error handling', + 'error_mode' => 'Guzzle stream error handling', + 'error_store' => 'Guzzle stream error handling', + ], + ]; + } + + /** + * @param mixed $value as passed via Request transfer options. + * + * @return array{0: string, 1: string|null} + */ + private static function normalizeTlsFileOption( + string $option, + #[\SensitiveParameter] + $value + ): array { + $passphrase = null; + + if (\is_array($value)) { + if (!isset($value[0]) || !\is_string($value[0])) { + throw new InvalidArgumentException(\sprintf('Invalid %s request option', $option)); + } + if (isset($value[1])) { + if (!\is_string($value[1])) { + throw new InvalidArgumentException(\sprintf('Invalid %s request option', $option)); + } + $passphrase = $value[1]; + } + $value = $value[0]; + } + + if (!\is_string($value)) { + throw new InvalidArgumentException(\sprintf('Invalid %s request option', $option)); + } + + return [$value, $passphrase]; + } + + private static function setTlsPassphrase( + #[\SensitiveParameter] + array &$options, + #[\SensitiveParameter] + ?string $passphrase, + string $option + ): void { + if ($passphrase === null) { + return; + } + + if (isset($options['ssl']['passphrase']) && $options['ssl']['passphrase'] !== $passphrase) { + throw new InvalidArgumentException(\sprintf('Cannot use different passphrases for cert and ssl_key with the stream handler; %s conflicts with an existing TLS passphrase.', $option)); + } + + $options['ssl']['passphrase'] = $passphrase; + } + /** * @param mixed $value as passed via Request transfer options. */ - private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void + private static function assertStreamTlsType(string $option, $value): void { - $uri = null; - - if (!\is_array($value)) { - $uri = $value; - } else { - $scheme = $request->getUri()->getScheme(); - if (isset($value[$scheme])) { - if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) { - $uri = $value[$scheme]; - } - } + if (!\is_string($value) || $value === '') { + throw new InvalidArgumentException(\sprintf('%s must be a non-empty string', $option)); } - if (!$uri) { - return; - } - - $parsed = $this->parse_proxy($uri); - $options['http']['proxy'] = $parsed['proxy']; - - if ($parsed['auth']) { - if (!isset($options['http']['header'])) { - $options['http']['header'] = []; - } - $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + if (Psr7\Utils::asciiToUpper($value) !== 'PEM') { + throw new InvalidArgumentException(\sprintf('The stream handler only supports "PEM" for the %s request option.', $option)); } } /** - * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + * @param mixed $value as passed via Request transfer options. */ - private function parse_proxy(string $url): array + private function applyProxy( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array &$context, + #[\SensitiveParameter] + $value + ): void { + $proxy = ProxyEnv::resolveProxySelection($request->getUri(), $value); + $proxyUri = $proxy->getProxy(); + if ($proxyUri === null) { + return; + } + + // Validate the whole proxy authority up front (ProxyOptions leans on + // Psr7\Rfc3986), so a malformed proxy fails the same way on every + // handler. PHP's HTTP stream wrapper can only carry HTTP over a + // TCP-family socket, so reject any scheme it can never execute instead + // of letting PHP fail with a misleading "unable to find the socket + // transport" error. The supported set is a scheme-less value, http, or + // a TCP-family transport (tcp://, ssl://, tls://, tlsv1.*) the build has. + $scheme = ProxyOptions::proxyScheme($proxyUri); + + if ($scheme === 'https') { + throw new InvalidArgumentException('HTTPS proxies are not supported by the stream handler.'); + } + + if (\in_array($scheme, ['socks4', 'socks4a', 'socks5', 'socks5h'], true)) { + throw new InvalidArgumentException('SOCKS proxies are not supported by the stream handler.'); + } + + // Only http or a TCP-family raw transport (tcp, ssl, tls, tlsv1.*) can + // carry HTTP through the stream wrapper; udp/unix/udg/ftp/ws and typos + // cannot on any build, so reject them as a caller error rather than + // install an unusable proxy. + if ($scheme !== 'http' && !self::isRawTransportName($scheme)) { + throw new InvalidArgumentException(\sprintf('The "%s" proxy scheme is not supported by the stream handler.', Psr7\DiagnosticValue::escape($scheme))); + } + + // A recognized SSL/TLS transport may still be absent from this build + // (tls:// without OpenSSL, tlsv1.3:// on OpenSSL < 1.1.1); that is + // build-specific, so it is a RequestException, distinct from the caller + // error above. http maps to tcp and tcp is always registered, so only + // the SSL/TLS family reaches the stream_get_transports() lookup. + if (!\in_array($scheme, ['http', 'tcp'], true) && !\in_array($scheme, \stream_get_transports(), true)) { + throw new RequestException(\sprintf('The "%s" proxy transport is not available in this PHP build.', Psr7\DiagnosticValue::escape($scheme)), $request); + } + + $parsed = $this->parseProxy($proxyUri, $scheme); + + // PHP streams do not expose separate proxy and origin header lists; + // the wrapper's CONNECT handling copies one textual match out of a + // generic user-header buffer and forwards the rest to the tunneled + // origin, so arbitrary first-class Proxy-Authorization values cannot + // be routed safely. Fail closed before any stream is created. + if ($request->hasHeader('Proxy-Authorization')) { + throw new InvalidArgumentException('Proxy-Authorization request headers are not supported through the stream handler; configure credentials in the proxy URI or use a cURL handler.'); + } + + $context['http']['proxy'] = $parsed['proxy']; + + if ($parsed['auth']) { + if (!isset($context['http']['header'])) { + $context['http']['header'] = ''; + } + $context['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + + /** + * Whether the scheme is a TCP-family transport name that can carry HTTP + * for a proxy (tcp, ssl, tls, tlsv1.*), regardless of build support. + */ + private static function isRawTransportName(string $scheme): bool + { + return \in_array($scheme, ['tcp', 'ssl', 'tls'], true) + || \preg_match('/^tlsv\d+(?:\.\d+)?$/D', $scheme) === 1; + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's + * stream context expects. + */ + private function parseProxy(string $url, string $scheme): array { - $parsed = \parse_url($url); - - if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') { - if (isset($parsed['host']) && isset($parsed['port'])) { - $auth = null; - if (isset($parsed['user']) && isset($parsed['pass'])) { - $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}"); - } - - return [ - 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", - 'auth' => $auth ? "Basic {$auth}" : null, - ]; - } + // applyProxy() has already validated the scheme, so only an http + // proxy needs translating to the tcp:// form the wrapper expects; a + // port-less proxy defaults to 1080 to match libcurl's HTTP default. + if ($scheme !== 'http') { + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + $parsed = \parse_url(\strpos($url, '://') === false ? 'http://'.$url : $url); + if (\is_array($parsed) && isset($parsed['host'])) { + $port = $parsed['port'] ?? 1080; + $user = $parsed['user'] ?? ''; + $pass = $parsed['pass'] ?? ''; + $auth = ($user !== '' || $pass !== '') ? 'Basic '.\base64_encode("{$user}:{$pass}") : null; + + return [ + 'proxy' => "tcp://{$parsed['host']}:{$port}", + 'auth' => $auth, + ]; } - // Return proxy as-is. return [ 'proxy' => $url, 'auth' => null, @@ -465,68 +1849,147 @@ class StreamHandler /** * @param mixed $value as passed via Request transfer options. */ - private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void - { - if ($value > 0) { - $options['http']['timeout'] = $value; - } + private function applyCryptoMethodOption( + #[\SensitiveParameter] + array &$context, + $value + ): void { + $context['ssl']['min_proto_version'] = TlsVersion::streamProtocolVersion('crypto_method', $value); } /** * @param mixed $value as passed via Request transfer options. */ - private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void - { + private function applyCryptoMethodMaxOption( + #[\SensitiveParameter] + array &$context, + $value + ): void { + $context['ssl']['max_proto_version'] = TlsVersion::streamProtocolVersion('crypto_method_max', $value); + } + + private static function assertTlsVersionRangeForOptions( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): void { + if (!isset($options['crypto_method_max'])) { + return; + } + + $cryptoMethod = $options['crypto_method'] ?? null; + if ($cryptoMethod === null && 'https' === $request->getUri()->getScheme()) { + $cryptoMethod = \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + + TlsVersion::assertRange($cryptoMethod, $options['crypto_method_max']); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function applyVerifyOption( + #[\SensitiveParameter] + array &$context, + $value + ): void { if ($value === false) { - $options['ssl']['verify_peer'] = false; - $options['ssl']['verify_peer_name'] = false; + $context['ssl']['verify_peer'] = false; + $context['ssl']['verify_peer_name'] = false; return; } if (\is_string($value)) { - $options['ssl']['cafile'] = $value; + $context['ssl']['cafile'] = $value; if (!\file_exists($value)) { - throw new \RuntimeException("SSL CA bundle not found: $value"); + throw new \RuntimeException(\sprintf('SSL CA bundle not found: %s', Psr7\DiagnosticValue::escape($value))); } } elseif ($value !== true) { - throw new \InvalidArgumentException('Invalid verify request option'); + throw new InvalidArgumentException('Invalid verify request option'); } - $options['ssl']['verify_peer'] = true; - $options['ssl']['verify_peer_name'] = true; - $options['ssl']['allow_self_signed'] = false; + $context['ssl']['verify_peer'] = true; + $context['ssl']['verify_peer_name'] = true; + $context['ssl']['allow_self_signed'] = false; } /** * @param mixed $value as passed via Request transfer options. */ - private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void - { - if (\is_array($value)) { - $options['ssl']['passphrase'] = $value[1]; - $value = $value[0]; - } + private function applyCertOption( + #[\SensitiveParameter] + array &$context, + #[\SensitiveParameter] + $value + ): void { + [$value, $passphrase] = self::normalizeTlsFileOption('cert', $value); if (!\file_exists($value)) { - throw new \RuntimeException("SSL certificate not found: {$value}"); + throw new \RuntimeException(\sprintf('SSL certificate not found: %s', Psr7\DiagnosticValue::escape($value))); } - $options['ssl']['local_cert'] = $value; + self::setTlsPassphrase($context, $passphrase, 'cert'); + $context['ssl']['local_cert'] = $value; } /** * @param mixed $value as passed via Request transfer options. */ - private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void + private function applyCertTypeOption($value): void { + self::assertStreamTlsType('cert_type', $value); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function applySslKeyOption( + #[\SensitiveParameter] + array &$context, + #[\SensitiveParameter] + $value + ): void { + [$value, $passphrase] = self::normalizeTlsFileOption('ssl_key', $value); + + if (!\file_exists($value)) { + throw new \RuntimeException(\sprintf('SSL private key not found: %s', Psr7\DiagnosticValue::escape($value))); + } + + self::setTlsPassphrase($context, $passphrase, 'ssl_key'); + $context['ssl']['local_pk'] = $value; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function applySslKeyTypeOption($value): void + { + self::assertStreamTlsType('ssl_key_type', $value); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function applyProgressOption($value, array &$params): void + { + if (!\is_callable($value)) { + throw new InvalidArgumentException('progress client option must be callable'); + } + self::addNotification( $params, - static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + static function ($code, $a, $b, $c, $transferred, $total) use ($value): void { if ($code == \STREAM_NOTIFY_PROGRESS) { // The upload progress cannot be determined. Use 0 for cURL compatibility: // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html - $value($total, $transferred, 0, 0); + $value( + TransferByteCounter::progressValueToInt($total), + TransferByteCounter::progressValueToInt($transferred), + 0, + 0 + ); } } ); @@ -535,34 +1998,38 @@ class StreamHandler /** * @param mixed $value as passed via Request transfer options. */ - private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void - { + private function applyDebugOption( + #[\SensitiveParameter] + RequestInterface $request, + $value, + array &$params + ): void { if ($value === false) { return; } static $map = [ - \STREAM_NOTIFY_CONNECT => 'CONNECT', + \STREAM_NOTIFY_CONNECT => 'CONNECT', \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', - \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', - \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', - \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', - \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', - \STREAM_NOTIFY_PROGRESS => 'PROGRESS', - \STREAM_NOTIFY_FAILURE => 'FAILURE', - \STREAM_NOTIFY_COMPLETED => 'COMPLETED', - \STREAM_NOTIFY_RESOLVE => 'RESOLVE', + \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + \STREAM_NOTIFY_PROGRESS => 'PROGRESS', + \STREAM_NOTIFY_FAILURE => 'FAILURE', + \STREAM_NOTIFY_COMPLETED => 'COMPLETED', + \STREAM_NOTIFY_RESOLVE => 'RESOLVE', ]; static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; $value = Utils::debugResource($value); - $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + $ident = $request->getMethod().' '.$request->getUri()->withFragment(''); self::addNotification( $params, static function (int $code, ...$passed) use ($ident, $value, $map, $args): void { \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); foreach (\array_filter($passed) as $i => $v) { - \fwrite($value, $args[$i] . ': "' . $v . '" '); + \fwrite($value, $args[$i].': "'.$v.'" '); } \fwrite($value, "\n"); } @@ -577,14 +2044,14 @@ class StreamHandler } else { $params['notification'] = self::callArray([ $params['notification'], - $notify + $notify, ]); } } private static function callArray(array $functions): callable { - return static function (...$args) use ($functions) { + return static function (...$args) use ($functions): void { foreach ($functions as $fn) { $fn(...$args); } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamTlsSessionCache.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamTlsSessionCache.php new file mode 100644 index 000000000..8abe67d25 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/StreamTlsSessionCache.php @@ -0,0 +1,583 @@ + true, + 'session_cache_size' => true, + 'session_data' => true, + 'session_get_cb' => true, + 'session_id_context' => true, + 'session_new_cb' => true, + 'session_remove_cb' => true, + 'session_stream' => true, + 'session_timeout' => true, + ]; + + private const USER_MANAGED_PSK_OPTIONS = [ + 'psk_client_cb' => true, + 'psk_server_cb' => true, + ]; + + /** + * TLS 1.3 early data (0-RTT) options are deliberately never shared: an + * injected cached session would let PHP send the replayable early data + * payload ahead of the HTTP request. + */ + private const USER_MANAGED_EARLY_DATA_OPTIONS = [ + 'early_data' => true, + 'early_data_cb' => true, + 'max_early_data' => true, + ]; + + /** + * File/path-bearing SSL context options are deliberately not shared: their + * contents can change outside this handler. + */ + private const PATH_OPTIONS = [ + 'SNI_server_certs' => true, + 'cafile' => true, + 'capath' => true, + 'dh_param' => true, + 'local_cert' => true, + 'local_pk' => true, + ]; + + private const CERT_CAPTURE_OPTIONS = [ + 'capture_peer_cert' => true, + 'capture_peer_cert_chain' => true, + ]; + + /** + * Scalar custom SSL context options that are known to be self-contained + * and safe to include in the TLS session cache identity. + */ + private const CUSTOM_SCALAR_KEYABLE_OPTIONS = [ + 'SNI_enabled' => true, + 'allow_self_signed' => true, + 'alpn_protocols' => true, + 'ciphers' => true, + 'crypto_method' => true, + 'disable_compression' => true, + 'max_proto_version' => true, + 'min_proto_version' => true, + 'peer_name' => true, + 'security_level' => true, + 'verify_depth' => true, + 'verify_peer' => true, + 'verify_peer_name' => true, + ]; + + private const INVALID_PEER_FINGERPRINT_REASON = 'the SSL context option "peer_fingerprint" must be a string or a non-empty, flat array with string algorithm names and string fingerprints.'; + + private int $maxKeys; + + /** + * @var array> + */ + private array $sessions = []; + + public function __construct(int $maxKeys) + { + if ($maxKeys < 1) { + throw new InvalidArgumentException('maxKeys must be a positive integer.'); + } + + $this->maxKeys = $maxKeys; + } + + public static function isSupported(): bool + { + // Any PHP 8.6 build qualifies, including pre-release and nightly + // builds; the class check keeps unstable builds that do not carry + // the final API fail-closed. + return \PHP_VERSION_ID >= 80600 && \class_exists(Session::class, false); + } + + /** + * @param array $ssl The assembled 'ssl' stream context array. + * @param array $customSsl The user-supplied stream_context['ssl'] array. + */ + public static function unsupportedContextReason(array $ssl, array $customSsl = []): ?string + { + foreach ($customSsl as $key => $value) { + if ( + !isset(self::CUSTOM_SCALAR_KEYABLE_OPTIONS[$key]) + && !isset(self::PATH_OPTIONS[$key]) + && !isset(self::USER_MANAGED_SESSION_OPTIONS[$key]) + && !isset(self::USER_MANAGED_PSK_OPTIONS[$key]) + && !isset(self::USER_MANAGED_EARLY_DATA_OPTIONS[$key]) + && !isset(self::CERT_CAPTURE_OPTIONS[$key]) + && $key !== 'no_ticket' + && $key !== 'peer_fingerprint' + ) { + return \sprintf('the custom SSL context option "%s" is not known to be safe for TLS session sharing.', Psr7\DiagnosticValue::escape((string) $key)); + } + } + + foreach ($ssl as $key => $value) { + if (isset(self::PATH_OPTIONS[$key])) { + return \sprintf('the SSL context option "%s" uses file or path state that cannot be safely shared.', Psr7\DiagnosticValue::escape((string) $key)); + } + + if (isset(self::USER_MANAGED_SESSION_OPTIONS[$key])) { + return \sprintf('the SSL context option "%s" is user-managed TLS session state.', Psr7\DiagnosticValue::escape((string) $key)); + } + + if (isset(self::USER_MANAGED_PSK_OPTIONS[$key])) { + return \sprintf('the SSL context option "%s" is user-managed TLS PSK state.', Psr7\DiagnosticValue::escape((string) $key)); + } + + if (isset(self::USER_MANAGED_EARLY_DATA_OPTIONS[$key])) { + return \sprintf('the SSL context option "%s" is user-managed TLS early data state.', Psr7\DiagnosticValue::escape((string) $key)); + } + + if (isset(self::CERT_CAPTURE_OPTIONS[$key]) && $value) { + return \sprintf('the SSL context option "%s" requires a fresh peer certificate handshake.', Psr7\DiagnosticValue::escape((string) $key)); + } + + if ($key === 'no_ticket' && $value) { + return 'the SSL context option "no_ticket" disables TLS ticket sharing.'; + } + + if ($key === 'peer_fingerprint') { + if (!self::isPeerFingerprint($value)) { + return self::INVALID_PEER_FINGERPRINT_REASON; + } + + continue; + } + + if (!self::isCanonicalScalar($value)) { + return \sprintf('the SSL context option "%s" cannot be safely included in the TLS session cache identity.', Psr7\DiagnosticValue::escape((string) $key)); + } + } + + return null; + } + + /** + * Builds the non-secret lookup key: host/port plus every TLS parameter that + * defines the verification/identity context. Keyed on the connection host + * a transport reads, with numeric IPv4 spellings folded to one dotted + * quad; names are never resolved. Secret material is excluded. + * + * @param array $ssl The assembled 'ssl' stream context array. + */ + public static function peerKey( + string $host, + ?int $port, + #[\SensitiveParameter] + array $ssl + ): string { + $identity = [ + 'schema' => 'guzzle-stream-tls-session-v1', + 'runtime' => self::runtimeIdentity(), + 'peer' => [ + 'host' => self::canonicalHost($host), + 'port' => $port, + ], + 'ssl' => self::canonicalPeerSslContext($ssl), + ]; + + return \hash('sha256', \serialize($identity)); + } + + /** + * Builds the secret-aware credential fingerprint matched in constant time, + * keeping the passphrase out of the loggable peer key. + * + * Every credential-bearing context is currently rejected by + * unsupportedContextReason() before sharing, so fingerprints can only + * diverge if the allow-lists are ever widened; the constant-time match is + * retained as defense in depth for that case. + * + * @param array $ssl The assembled 'ssl' stream context array. + */ + public static function credentialFingerprint(array $ssl): string + { + $material = [ + 'local_cert' => isset($ssl['local_cert']) && \is_string($ssl['local_cert']) ? self::pathIdentity($ssl['local_cert']) : null, + 'local_pk' => isset($ssl['local_pk']) && \is_string($ssl['local_pk']) ? self::pathIdentity($ssl['local_pk']) : null, + 'passphrase' => isset($ssl['passphrase']) && \is_string($ssl['passphrase']) ? $ssl['passphrase'] : null, + ]; + + return \hash('sha256', \serialize($material)); + } + + public function find(string $key, string $credentials): ?Session + { + if (!isset($this->sessions[$key])) { + return null; + } + + $now = \time(); + $entries = $this->sessions[$key]; + $found = null; + + foreach ($entries as $i => $entry) { + if ($entry['expiresAt'] <= $now) { + unset($entries[$i]); + continue; + } + + if (!\hash_equals($entry['credentials'], $credentials)) { + continue; + } + + if (!$entry['session']->isResumable()) { + unset($entries[$i]); + continue; + } + + $found = $entry['session']; + + // TLS 1.3 tickets are single-use; consume on take. + if ($entry['singleUse']) { + unset($entries[$i]); + } + + break; + } + + if ($entries === []) { + unset($this->sessions[$key]); + } else { + $this->sessions[$key] = \array_values($entries); + if ($found !== null) { + $this->touch($key); + } + } + + return $found; + } + + public function store( + string $key, + string $credentials, + #[\SensitiveParameter] + Session $session + ): void { + if (!$session->isResumable()) { + return; + } + + $expiresAt = self::expiry($session); + if ($expiresAt === null) { + return; + } + + $list = $this->sessions[$key] ?? []; + $list[] = [ + 'session' => $session, + 'credentials' => $credentials, + 'expiresAt' => $expiresAt, + 'singleUse' => self::isTls13($session), + ]; + + if (\count($list) > self::MAX_SESSIONS_PER_KEY) { + $list = \array_slice($list, -self::MAX_SESSIONS_PER_KEY); + } + + $this->sessions[$key] = $list; + $this->touch($key); + $this->evictExcessKeys(); + } + + private function touch(string $key): void + { + if (!isset($this->sessions[$key])) { + return; + } + + $value = $this->sessions[$key]; + unset($this->sessions[$key]); + $this->sessions[$key] = $value; + } + + private function evictExcessKeys(): void + { + while (\count($this->sessions) > $this->maxKeys) { + \array_shift($this->sessions); + } + } + + private static function isTls13( + #[\SensitiveParameter] + Session $session + ): bool { + $protocol = $session->getProtocol(); + + return $protocol !== null && Psr7\Utils::caselessContains($protocol, '1.3'); + } + + /** + * Returns the absolute expiry timestamp, or null when the session must + * not be cached. + */ + private static function expiry( + #[\SensitiveParameter] + Session $session + ): ?int { + return self::expiryFromLifetimes( + self::isTls13($session), + $session->hasTicket(), + $session->getTicketLifetimeHint(), + $session->getTimeout(), + $session->getCreatedAt(), + \time() + ); + } + + /** + * Decides the absolute expiry timestamp from a session's scalar lifetime + * attributes, or null when the session must not be cached. + * + * A non-positive session timeout is never cached. TLS 1.3 resumption + * requires a New Session Ticket, so a TLS 1.3 session without a ticket + * or without a positive RFC 8446 ticket lifetime is not cached, and the + * ticket lifetime bounds the expiry together with the session timeout + * and the seven-day cap. Pre-TLS-1.3 tickets treat a zero or missing + * lifetime hint as unspecified per RFC 5077, so only a positive hint + * tightens the session timeout and the one-day cap. An expiry that has + * already passed is not cached. + */ + private static function expiryFromLifetimes(bool $isTls13, bool $hasTicket, ?int $ticketLifetimeHint, int $timeout, int $createdAt, int $now): ?int + { + if ($timeout <= 0) { + return null; + } + + $lifetime = \min($timeout, $isTls13 ? self::MAX_TLS13_LIFETIME : self::MAX_PRE_TLS13_LIFETIME); + + if ($isTls13) { + if (!$hasTicket || $ticketLifetimeHint === null || $ticketLifetimeHint <= 0) { + return null; + } + + $lifetime = \min($lifetime, $ticketLifetimeHint); + } elseif ($hasTicket && $ticketLifetimeHint !== null && $ticketLifetimeHint > 0) { + $lifetime = \min($lifetime, $ticketLifetimeHint); + } + + $expiresAt = $createdAt + $lifetime; + + return $expiresAt > $now ? $expiresAt : null; + } + + private static function runtimeIdentity(): array + { + return [ + 'php' => \PHP_VERSION_ID, + 'openssl' => \defined('OPENSSL_VERSION_NUMBER') ? \OPENSSL_VERSION_NUMBER : null, + 'trust' => [ + 'openssl.cafile' => (string) \ini_get('openssl.cafile'), + 'openssl.capath' => (string) \ini_get('openssl.capath'), + 'SSL_CERT_FILE' => (string) \getenv('SSL_CERT_FILE'), + 'SSL_CERT_DIR' => (string) \getenv('SSL_CERT_DIR'), + 'cwd' => \getcwd(), + ], + ]; + } + + /** + * Returns the host identity hashed into the cache key. Valid bracketed + * IPv6 literals are canonicalized to their RFC 5952 form so equivalent + * spellings of one address share a single session entry; other valid + * hosts, such as reg-names and IPvFuture literals, fall back to ASCII + * case folding. Text that is not a valid RFC 3986 host, such as + * zone-bearing or malformed bracketed literals, must never become a + * usable cache key, so it is rejected. + */ + private static function canonicalHost(string $host): string + { + if (!Psr7\Rfc3986::isValidHost($host)) { + throw new InvalidArgumentException('Hosts used in the TLS session cache identity must be valid RFC 3986 hosts.'); + } + + return HostIdentity::canonicalHost($host); + } + + private static function canonicalPeerSslContext( + #[\SensitiveParameter] + array $ssl + ): array { + $context = []; + + foreach ($ssl as $key => $value) { + if (isset(self::USER_MANAGED_SESSION_OPTIONS[$key]) || isset(self::USER_MANAGED_PSK_OPTIONS[$key]) || isset(self::USER_MANAGED_EARLY_DATA_OPTIONS[$key]) || $key === 'passphrase') { + continue; + } + + if ($key === 'peer_name' && \is_string($value)) { + $context[$key] = ['string', self::canonicalHost($value)]; + + continue; + } + + if ($key === 'peer_fingerprint') { + $context[$key] = self::canonicalPeerFingerprint($value); + + continue; + } + + if (\in_array($key, ['cafile', 'capath', 'local_cert', 'local_pk'], true) && \is_string($value)) { + $context[$key] = self::pathIdentity($value); + + continue; + } + + $context[$key] = self::canonicalScalar($value); + } + + \ksort($context); + + return $context; + } + + private static function pathIdentity(string $path): array + { + $realPath = \realpath($path); + + return [ + 'path', + $realPath !== false ? $realPath : $path, + ]; + } + + /** + * @param mixed $value + */ + private static function isCanonicalScalar($value): bool + { + return $value === null + || \is_bool($value) + || \is_int($value) + || \is_float($value) + || \is_string($value); + } + + /** + * @param mixed $value + */ + private static function isPeerFingerprint($value): bool + { + if (\is_string($value)) { + return true; + } + + if (!\is_array($value) || $value === []) { + return false; + } + + foreach ($value as $algorithm => $fingerprint) { + if (!\is_string($algorithm) || !\is_string($fingerprint)) { + return false; + } + } + + return true; + } + + /** + * @param mixed $value + * + * @return array{0: string, 1: mixed} + */ + private static function canonicalScalar($value): array + { + if ($value === null) { + return ['null', null]; + } + + if (\is_bool($value)) { + return ['bool', $value]; + } + + if (\is_int($value)) { + return ['int', $value]; + } + + if (\is_float($value)) { + return ['float', \bin2hex(\pack('E', $value))]; + } + + if (\is_string($value)) { + return ['string', $value]; + } + + throw new InvalidArgumentException('SSL context values used in the TLS session cache identity must be scalar or null.'); + } + + /** + * @param mixed $value + * + * @return array{0: string, 1: mixed} + */ + private static function canonicalPeerFingerprint($value): array + { + if (!self::isPeerFingerprint($value)) { + throw new InvalidArgumentException(self::INVALID_PEER_FINGERPRINT_REASON); + } + + if (\is_string($value)) { + return ['string', $value]; + } + + if (!\is_array($value)) { + throw new InvalidArgumentException(self::INVALID_PEER_FINGERPRINT_REASON); + } + + $items = []; + foreach ($value as $algorithm => $fingerprint) { + $items[$algorithm] = ['string', $fingerprint]; + } + \ksort($items, \SORT_STRING); + + return ['map', $items]; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Timeout.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Timeout.php new file mode 100644 index 000000000..0c9da6cd9 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/Timeout.php @@ -0,0 +1,41 @@ + 0 && $milliseconds === 0) { + throw new InvalidArgumentException($option.' must be 0 or greater than or equal to 0.001 seconds'); + } + + return $milliseconds; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/TlsVersion.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/TlsVersion.php new file mode 100644 index 000000000..90cf7c8f5 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/TlsVersion.php @@ -0,0 +1,70 @@ + \PHP_INT_MAX + || (\PHP_INT_SIZE === 8 && $value >= (float) \PHP_INT_MAX) + ) { + throw new \OverflowException('Progress byte count exceeds the maximum integer size supported on this platform'); + } + + $intValue = (int) $value; + if ($intValue < 0) { + throw new \OverflowException('Progress byte count exceeds the maximum integer size supported on this platform'); + } + + return $intValue; + } + + public static function add(int $current, int $delta, string $message): int + { + if ($current < 0 || $delta < 0) { + throw new \OverflowException($message); + } + + if ($delta > \PHP_INT_MAX - $current) { + throw new \OverflowException($message); + } + + return $current + $delta; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/TransportStream.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/TransportStream.php new file mode 100644 index 000000000..1fe673f98 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/TransportStream.php @@ -0,0 +1,48 @@ +stream = $stream; + } + + public function __clone() + { + // Detaching a clone must not detach this body's transport. + $this->stream = clone $this->stream; + } + + public function read(int $length): string + { + $buffered = $this->stream->getMetadata('unread_bytes'); + + return $this->stream->read(\is_int($buffered) && $buffered > 0 ? \min($length, $buffered) : $length); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/UriDiagnostic.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/UriDiagnostic.php new file mode 100644 index 000000000..61028a27f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Handler/UriDiagnostic.php @@ -0,0 +1,39 @@ +getQuery(); + if ($query !== '') { + $message = \str_replace('?'.$query, '', $message); + } + + $fragment = $uri->getFragment(); + if ($fragment !== '') { + $message = \str_replace('#'.$fragment, '', $message); + } + + return Psr7\DiagnosticValue::escape($message); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php index e0a1d1191..ed49d7e4e 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -1,8 +1,11 @@ */ - private $stack = []; + private array $stack = []; /** - * @var (callable(RequestInterface, array): PromiseInterface)|null + * @var (callable&THandler)|null */ private $cached; @@ -33,22 +40,25 @@ class HandlerStack * Creates a default handler stack that can be used by clients. * * The returned handler will wrap the provided handler or use the most - * appropriate default handler for your system. The returned HandlerStack has - * support for cookies, redirects, HTTP error exceptions, and preparing a body - * before sending. + * appropriate default handler for your system. The returned HandlerStack + * has support for authentication, cookies, redirects, HTTP error + * exceptions, and preparing a body before sending. * * The returned handler stack can be passed to a client in the "handler" * option. * - * @param (callable(RequestInterface, array): PromiseInterface)|null $handler HTTP handler function to use with the stack. If no - * handler is provided, the best handler for your - * system will be utilized. + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler HTTP handler function to use with the stack. If no + * handler is provided, the best handler for your + * system will be utilized. + * + * @return self): PromiseInterface> */ public static function create(?callable $handler = null): self { $stack = new self($handler ?: Utils::chooseHandler()); $stack->push(Middleware::httpErrors(), 'http_errors'); $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::auth(), 'auth'); $stack->push(Middleware::cookies(), 'cookies'); $stack->push(Middleware::prepareBody(), 'prepare_body'); @@ -56,9 +66,9 @@ class HandlerStack } /** - * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + * @param (callable&THandler)|null $handler Underlying handler. */ - public function __construct(callable $handler = null) + public function __construct(?callable $handler = null) { $this->handler = $handler; } @@ -66,50 +76,23 @@ class HandlerStack /** * Invokes the handler stack as a composed handler * - * @return ResponseInterface|PromiseInterface + * @return PromiseInterface */ - public function __invoke(RequestInterface $request, array $options) - { + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) { $handler = $this->resolve(); return $handler($request, $options); } - /** - * Dumps a string representation of the stack. - * - * @return string - */ - public function __toString() - { - $depth = 0; - $stack = []; - - if ($this->handler !== null) { - $stack[] = "0) Handler: " . $this->debugCallable($this->handler); - } - - $result = ''; - foreach (\array_reverse($this->stack) as $tuple) { - $depth++; - $str = "{$depth}) Name: '{$tuple[1]}', "; - $str .= "Function: " . $this->debugCallable($tuple[0]); - $result = "> {$str}\n{$result}"; - $stack[] = $str; - } - - foreach (\array_keys($stack) as $k) { - $result .= "< {$stack[$k]}\n"; - } - - return $result; - } - /** * Set the HTTP handler that actually returns a promise. * - * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and - * returns a Promise. + * @param callable&THandler $handler Accepts a request and array of options and returns a value expected by the stack. */ public function setHandler(callable $handler): void { @@ -122,14 +105,14 @@ class HandlerStack */ public function hasHandler(): bool { - return $this->handler !== null ; + return $this->handler !== null; } /** * Unshift a middleware to the bottom of the stack. * - * @param callable(callable): callable $middleware Middleware function - * @param string $name Name to register for this middleware. + * @param callable(callable&THandler): (callable&THandler) $middleware Middleware function + * @param string $name Name to register for this middleware. */ public function unshift(callable $middleware, ?string $name = null): void { @@ -140,8 +123,8 @@ class HandlerStack /** * Push a middleware to the top of the stack. * - * @param callable(callable): callable $middleware Middleware function - * @param string $name Name to register for this middleware. + * @param callable(callable&THandler): (callable&THandler) $middleware Middleware function + * @param string $name Name to register for this middleware. */ public function push(callable $middleware, string $name = ''): void { @@ -152,9 +135,9 @@ class HandlerStack /** * Add a middleware before another middleware by name. * - * @param string $findName Middleware to find - * @param callable(callable): callable $middleware Middleware function - * @param string $withName Name to register for this middleware. + * @param string $findName Middleware to find + * @param callable(callable&THandler): (callable&THandler) $middleware Middleware function + * @param string $withName Name to register for this middleware. */ public function before(string $findName, callable $middleware, string $withName = ''): void { @@ -164,9 +147,9 @@ class HandlerStack /** * Add a middleware after another middleware by name. * - * @param string $findName Middleware to find - * @param callable(callable): callable $middleware Middleware function - * @param string $withName Name to register for this middleware. + * @param string $findName Middleware to find + * @param callable(callable&THandler): (callable&THandler) $middleware Middleware function + * @param string $withName Name to register for this middleware. */ public function after(string $findName, callable $middleware, string $withName = ''): void { @@ -176,20 +159,35 @@ class HandlerStack /** * Remove a middleware by instance or name from the stack. * - * @param callable|string $remove Middleware to remove by instance or name. + * @param (callable(callable&THandler): (callable&THandler))|string $remove Middleware to remove by instance or name. */ public function remove($remove): void { - if (!is_string($remove) && !is_callable($remove)) { - trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + if (!\is_string($remove) && !\is_callable($remove)) { + // TODO: Move this to the parameter definition in 9.0. + throw new \TypeError(__METHOD__.'(): Argument #1 ($remove) must be of type callable|string'); } $this->cached = null; - $idx = \is_callable($remove) ? 0 : 1; + + if (\is_string($remove)) { + $count = \count($this->stack); + $this->stack = \array_values(\array_filter( + $this->stack, + static function (array $tuple) use ($remove): bool { + return $tuple[1] !== $remove; + } + )); + + if ($count !== \count($this->stack) || !\is_callable($remove)) { + return; + } + } + $this->stack = \array_values(\array_filter( $this->stack, - static function ($tuple) use ($idx, $remove) { - return $tuple[$idx] !== $remove; + static function (array $tuple) use ($remove): bool { + return $tuple[0] !== $remove; } )); } @@ -197,7 +195,7 @@ class HandlerStack /** * Compose the middleware and handler into a single callable function. * - * @return callable(RequestInterface, array): PromiseInterface + * @return callable&THandler */ public function resolve(): callable { @@ -206,9 +204,20 @@ class HandlerStack throw new \LogicException('No handler has been specified'); } + if (!\is_callable($prev)) { + throw new \LogicException('Handler must be callable'); + } + foreach (\array_reverse($this->stack) as $fn) { - /** @var callable(RequestInterface, array): PromiseInterface $prev */ + if (!\is_array($fn) || !\array_key_exists(0, $fn) || !\is_callable($fn[0])) { + throw new \LogicException('Middleware must be callable'); + } + $prev = $fn[0]($prev); + + if (!\is_callable($prev)) { + throw new \LogicException('Middleware must return a callable'); + } } $this->cached = $prev; @@ -217,6 +226,15 @@ class HandlerStack return $this->cached; } + public function __unserialize(array $data): void + { + $this->handler = null; + $this->stack = []; + $this->cached = null; + + throw new \LogicException(static::class.' should never be unserialized'); + } + private function findByName(string $name): int { foreach ($this->stack as $k => $v) { @@ -225,11 +243,13 @@ class HandlerStack } } - throw new \InvalidArgumentException("Middleware not found: $name"); + throw new \InvalidArgumentException(\sprintf('Middleware not found: %s', DiagnosticValue::escape($name))); } /** * Splices a function into the middleware list at a specific position. + * + * @param callable(callable&THandler): (callable&THandler) $middleware */ private function splice(string $findName, string $withName, callable $middleware, bool $before): void { @@ -251,25 +271,4 @@ class HandlerStack \array_splice($this->stack, $idx, 1, $replacement); } } - - /** - * Provides a debug string for a given callable. - * - * @param callable|string $fn Function to write as a string. - */ - private function debugCallable($fn): string - { - if (\is_string($fn)) { - return "callable({$fn})"; - } - - if (\is_array($fn)) { - return \is_string($fn[0]) - ? "callable({$fn[0]}::{$fn[1]})" - : "callable(['" . \get_class($fn[0]) . "', '{$fn[1]}'])"; - } - - /** @var object $fn */ - return 'callable(' . \spl_object_hash($fn) . ')'; - } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HostIdentity.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HostIdentity.php new file mode 100644 index 000000000..5bd6c2754 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/HostIdentity.php @@ -0,0 +1,233 @@ + 2 && \ctype_xdigit(\substr($last, 2)); + } + + /** + * Returns the four-byte binary form of a host that a transport reads as a + * numeric IPv4 address, or null when it reads it as a name. + * + * The shape test is Handler\HostValidator::isNumericIpv4Host(); this + * method adds the range checks that predicate omits: every part but the + * last must fit one octet, and the last must fit the octets the earlier + * parts left. A trailing root dot is not swallowed, unlike libcurl 8.21.0 + * and later, because assertRequestHost() rejects that spelling first. + */ + public static function numericIpv4ToBinary(string $host): ?string + { + if (!Handler\HostValidator::isNumericIpv4Host($host)) { + return null; + } + + $values = []; + foreach (\explode('.', $host) as $part) { + $values[] = self::numericIpv4PartValue($part); + } + + // Every accepted value is a whole number no larger than 0xFFFFFFFF, + // which a float holds exactly, so the arithmetic below is correct on a + // 32-bit build too, where the widest part overflows an integer. + $address = (float) \array_pop($values); + + $packed = ''; + foreach ($values as $value) { + if ($value > 255.0) { + return null; + } + + $packed .= \chr((int) $value); + } + + $width = 4 - \count($values); + if ($address >= 256.0 ** $width) { + return null; + } + + for ($shift = $width - 1; $shift >= 0; --$shift) { + $packed .= \chr((int) \fmod(\floor($address / 256.0 ** $shift), 256.0)); + } + + return $packed; + } + + /** + * Returns the value of one accepted part as a float, so a part filling + * all four octets such as 2130706433 stays exact on every integer width. + */ + private static function numericIpv4PartValue(string $part): float + { + if ($part[0] === '0' && isset($part[1]) && ($part[1] === 'x' || $part[1] === 'X')) { + return self::digitsValue((string) \substr($part, 2), 16); + } + + if ($part[0] === '0') { + return self::digitsValue($part, 8); + } + + return (float) $part; + } + + private static function digitsValue(string $digits, int $base): float + { + $value = 0.0; + foreach (\str_split($digits) as $digit) { + $value = $value * $base + \hexdec($digit); + if ($value > 0xFFFFFFFF) { + break; + } + } + + return $value; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Idn.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Idn.php new file mode 100644 index 000000000..cf2edb5f7 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Idn.php @@ -0,0 +1,87 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = $info['errors'] ?? 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { + return substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: '.implode(', ', $errors).')'; + } + + throw new InvalidArgumentException($errorMessage); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + + return $uri; + } + + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php index da499547f..e76352276 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -1,5 +1,7 @@ >>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; /** * @var string Template used to format log messages */ - private $template; + private string $template; /** * @param string $template Log message template @@ -72,10 +74,9 @@ class MessageFormatter implements MessageFormatterInterface { $cache = []; - /** @var string */ - return \preg_replace_callback( + $result = \preg_replace_callback( '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', - function (array $matches) use ($request, $response, $error, &$cache) { + function (array $matches) use ($request, $response, $error, &$cache): string { if (isset($cache[$matches[1]])) { return $cache[$matches[1]]; } @@ -90,9 +91,9 @@ class MessageFormatter implements MessageFormatterInterface break; case 'req_headers': $result = \trim($request->getMethod() - . ' ' . $request->getRequestTarget()) - . ' HTTP/' . $request->getProtocolVersion() . "\r\n" - . $this->headers($request); + .' '.$request->getRequestTarget(), " \n\r\t\0\x0B") + .' HTTP/'.$request->getProtocolVersion()."\r\n" + .$this->headers($request); break; case 'res_headers': $result = $response ? @@ -101,7 +102,7 @@ class MessageFormatter implements MessageFormatterInterface $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase() - ) . "\r\n" . $this->headers($response) + )."\r\n".$this->headers($response) : 'NULL'; break; case 'req_body': @@ -176,11 +177,19 @@ class MessageFormatter implements MessageFormatterInterface } } + $result = (string) $result; $cache[$matches[1]] = $result; + return $result; }, $this->template ); + + if ($result === null) { + throw new \RuntimeException('Unable to format message: '.\preg_last_error_msg()); + } + + return $result; } /** @@ -190,9 +199,9 @@ class MessageFormatter implements MessageFormatterInterface { $result = ''; foreach ($message->getHeaders() as $name => $values) { - $result .= $name . ': ' . \implode(', ', $values) . "\r\n"; + $result .= $name.': '.\implode(', ', $values)."\r\n"; } - return \trim($result); + return \trim($result, " \n\r\t\0\x0B"); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php index a39ac248e..b72e24c00 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -1,5 +1,7 @@ ): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) + */ + public static function auth(bool $reuseChallenges = true): callable + { + return static function (callable $handler) use ($reuseChallenges): AuthMiddleware { + return new AuthMiddleware($handler, null, $reuseChallenges); + }; + } + /** * Middleware that adds cookies to requests. * * The options array must be set to a CookieJarInterface in order to use * cookies. This is typically handled for you by a client. * - * @return callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ public static function cookies(): callable { return static function (callable $handler): callable { - return static function ($request, array $options) use ($handler) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($handler): PromiseInterface { if (empty($options['cookies'])) { return $handler($request, $options); - } elseif (!($options['cookies'] instanceof CookieJarInterface)) { + } elseif (!$options['cookies'] instanceof CookieJarInterface) { throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface'); } $cookieJar = $options['cookies']; $request = $cookieJar->withCookieHeader($request); + return $handler($request, $options) ->then( - static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + static function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($cookieJar, $request): ResponseInterface { $cookieJar->extractCookies($request, $response); + return $response; } ); @@ -51,22 +83,31 @@ final class Middleware * * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. * - * @return callable(callable): callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ - public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable + public static function httpErrors(?BodySummarizerInterface $bodySummarizer = null): callable { return static function (callable $handler) use ($bodySummarizer): callable { - return static function ($request, array $options) use ($handler, $bodySummarizer) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($handler, $bodySummarizer): PromiseInterface { if (empty($options['http_errors'])) { return $handler($request, $options); } + return $handler($request, $options)->then( - static function (ResponseInterface $response) use ($request, $bodySummarizer) { + static function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($request, $bodySummarizer): ResponseInterface { $code = $response->getStatusCode(); if ($code < 400) { return $response; } - throw RequestException::create($request, $response, null, [], $bodySummarizer); + throw RequestException::create($request, $response, null, $bodySummarizer); } ); }; @@ -76,9 +117,9 @@ final class Middleware /** * Middleware that pushes history data to an ArrayAccess container. * - * @param array|\ArrayAccess $container Container to hold the history (by reference). + * @param array}>|\ArrayAccess}> $container Container to hold the history (by reference). * - * @return callable(callable): callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) * * @throws \InvalidArgumentException if container is not an array or ArrayAccess. */ @@ -89,24 +130,37 @@ final class Middleware } return static function (callable $handler) use (&$container): callable { - return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($handler, &$container): PromiseInterface { return $handler($request, $options)->then( - static function ($value) use ($request, &$container, $options) { + static function ( + #[\SensitiveParameter] + ResponseInterface $value + ) use ($request, &$container, $options): ResponseInterface { $container[] = [ - 'request' => $request, + 'request' => $request, 'response' => $value, - 'error' => null, - 'options' => $options + 'error' => null, + 'options' => $options, ]; + return $value; }, - static function ($reason) use ($request, &$container, $options) { + static function ( + #[\SensitiveParameter] + $reason + ) use ($request, &$container, $options): PromiseInterface { $container[] = [ - 'request' => $request, + 'request' => $request, 'response' => null, - 'error' => $reason, - 'options' => $options + 'error' => $reason, + 'options' => $options, ]; + return P\Create::rejectionFor($reason); } ); @@ -115,22 +169,29 @@ final class Middleware } /** - * Middleware that invokes a callback before and after sending a request. + * Middleware that observes requests and responses as they flow through the + * stack without modifying them. This is useful for metrics, tracing, and + * debugging. * * The provided listener cannot modify or alter the response. It simply * "taps" into the chain to be notified before returning the promise. The * before listener accepts a request and options array, and the after * listener accepts a request, options array, and response promise. * - * @param callable $before Function to invoke before forwarding the request. - * @param callable $after Function invoked after forwarding. + * @param (callable(RequestInterface, array): mixed)|null $before Function to invoke before forwarding the request. + * @param (callable(RequestInterface, array, PromiseInterface): mixed)|null $after Function invoked after forwarding. * - * @return callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ - public static function tap(callable $before = null, callable $after = null): callable + public static function tap(?callable $before = null, ?callable $after = null): callable { return static function (callable $handler) use ($before, $after): callable { - return static function (RequestInterface $request, array $options) use ($handler, $before, $after) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($handler, $before, $after): PromiseInterface { if ($before) { $before($request, $options); } @@ -138,6 +199,7 @@ final class Middleware if ($after) { $after($request, $options, $response); } + return $response; }; }; @@ -146,7 +208,7 @@ final class Middleware /** * Middleware that handles request redirects. * - * @return callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ public static function redirect(): callable { @@ -162,15 +224,16 @@ final class Middleware * If no delay function is provided, a simple implementation of exponential * backoff will be utilized. * - * @param callable $decider Function that accepts the number of retries, - * a request, [response], and [exception] and - * returns true if the request is to be retried. - * @param callable $delay Function that accepts the number of retries and - * returns the number of milliseconds to delay. + * @param callable(int, RequestInterface, ResponseInterface|null, mixed): bool $decider Function that accepts the number of retries, + * a request, [response], and [rejection reason] + * and returns true if the request is to be retried. + * @param (callable(int, ResponseInterface|null, RequestInterface): int)|null $delay Function that accepts the number of retries, + * [response], and request, and returns the + * number of milliseconds to delay. * - * @return callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ - public static function retry(callable $decider, callable $delay = null): callable + public static function retry(callable $decider, ?callable $delay = null): callable { return static function (callable $handler) use ($decider, $delay): RetryMiddleware { return new RetryMiddleware($decider, $handler, $delay); @@ -181,33 +244,42 @@ final class Middleware * Middleware that logs requests, responses, and errors using a message * formatter. * - * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. * - * @param LoggerInterface $logger Logs messages. - * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. - * @param string $logLevel Level at which to log requests. - * - * @return callable Returns a function that accepts the next handler. + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ - public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable + public static function log(LoggerInterface $logger, MessageFormatterInterface $formatter, string $logLevel = 'info'): callable { - // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter - if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { - throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); - } - return static function (callable $handler) use ($logger, $formatter, $logLevel): callable { - return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options = [] + ) use ($handler, $logger, $formatter, $logLevel): PromiseInterface { return $handler($request, $options)->then( - static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface { + static function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($logger, $request, $formatter, $logLevel): ResponseInterface { $message = $formatter->format($request, $response); $logger->log($logLevel, $message); + return $response; }, - static function ($reason) use ($logger, $request, $formatter): PromiseInterface { - $response = $reason instanceof RequestException ? $reason->getResponse() : null; + /** + * @return PromiseInterface + */ + static function ( + #[\SensitiveParameter] + $reason + ) use ($logger, $request, $formatter): PromiseInterface { + $response = $reason instanceof ResponseException ? $reason->getResponse() : null; $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); $logger->error($message); + return P\Create::rejectionFor($reason); } ); @@ -218,6 +290,8 @@ final class Middleware /** * This middleware adds a default content-type if possible, a default * content-length or transfer-encoding header, and the expect header. + * + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ public static function prepareBody(): callable { @@ -230,13 +304,20 @@ final class Middleware * Middleware that applies a map function to the request before passing to * the next handler. * - * @param callable $fn Function that accepts a RequestInterface and returns - * a RequestInterface. + * @param callable(RequestInterface): RequestInterface $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + * + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ public static function mapRequest(callable $fn): callable { return static function (callable $handler) use ($fn): callable { - return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($handler, $fn): PromiseInterface { return $handler($fn($request), $options); }; }; @@ -246,13 +327,20 @@ final class Middleware * Middleware that applies a map function to the resolved promise's * response. * - * @param callable $fn Function that accepts a ResponseInterface and - * returns a ResponseInterface. + * @param callable(ResponseInterface): ResponseInterface $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + * + * @return callable((callable(RequestInterface, array): PromiseInterface)): (callable(RequestInterface, array): PromiseInterface) */ public static function mapResponse(callable $fn): callable { return static function (callable $handler) use ($fn): callable { - return static function (RequestInterface $request, array $options) use ($handler, $fn) { + return static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ) use ($handler, $fn): PromiseInterface { return $handler($request, $options)->then($fn); }; }; diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Multiplexing.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Multiplexing.php new file mode 100644 index 000000000..90501bac1 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/Multiplexing.php @@ -0,0 +1,28 @@ + */ class Pool implements PromisorInterface { - /** - * @var EachPromise - */ - private $each; + use NonSerializableTrait; /** - * @param ClientInterface $client Client used to send the requests. - * @param array|\Iterator $requests Requests or functions that return - * requests to send concurrently. - * @param array $config Associative array of options - * - concurrency: (int) Maximum number of requests to send concurrently - * - options: Array of request options to apply to each request. - * - fulfilled: (callable) Function to invoke when a request completes. - * - rejected: (callable) Function to invoke when a request is rejected. + * @var EachPromise */ - public function __construct(ClientInterface $client, $requests, array $config = []) - { + private EachPromise $each; + + /** + * @param ClientInterface $client Client used to send the requests. + * @param iterable): (ResponseInterface|PromiseInterface)> $requests Requests or functions that return responses or response promises. + * @param array{ + * concurrency?: int|(callable(int): int), + * options?: array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface, int|string): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface, int|string): mixed, + * on_stats?: callable(TransferStats, int|string): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface, int|string): mixed, + * progress?: callable(int, int, int, int, int|string): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * }, + * fulfilled?: callable(ResponseInterface, int|string, PromiseInterface): mixed, + * rejected?: callable(mixed, int|string, PromiseInterface): mixed + * } $config Pool configuration. + */ + public function __construct( + ClientInterface $client, + iterable $requests, + #[\SensitiveParameter] + array $config = [] + ) { if (!isset($config['concurrency'])) { $config['concurrency'] = 25; } @@ -51,24 +144,27 @@ class Pool implements PromisorInterface $opts = []; } - $iterable = P\Create::iterFor($requests); - $requests = static function () use ($iterable, $client, $opts) { - foreach ($iterable as $key => $rfn) { + $requestGenerator = static function () use ($requests, $client, $opts): \Generator { + foreach ($requests as $key => $rfn) { + $keyedOpts = self::keyedRequestOptions($opts, $key); + if ($rfn instanceof RequestInterface) { - yield $key => $client->sendAsync($rfn, $opts); + yield $key => $client->sendAsync($rfn, $keyedOpts); } elseif (\is_callable($rfn)) { - yield $key => $rfn($opts); + yield $key => $rfn($keyedOpts); } else { - throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.'); + throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr\Http\Message\ResponseInterface object.'); } } }; - $this->each = new EachPromise($requests(), $config); + $this->each = new EachPromise($requestGenerator(), $config); } /** * Get promise + * + * @return PromiseInterface */ public function promise(): PromiseInterface { @@ -83,18 +179,98 @@ class Pool implements PromisorInterface * as such, is NOT recommended when sending a large number or an * indeterminate number of requests concurrently. * - * @param ClientInterface $client Client used to send the requests - * @param array|\Iterator $requests Requests to send concurrently. - * @param array $options Passes through the options available in - * {@see \GuzzleHttp\Pool::__construct} + * @param ClientInterface $client Client used to send the requests + * @param iterable): (ResponseInterface|PromiseInterface)> $requests Requests or functions that return responses or response promises. + * @param array{ + * concurrency?: int|(callable(int): int), + * options?: array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface, int|string): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface, int|string): mixed, + * on_stats?: callable(TransferStats, int|string): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface, int|string): mixed, + * progress?: callable(int, int, int, int, int|string): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * }, + * fulfilled?: callable(ResponseInterface, int|string): mixed, + * rejected?: callable(mixed, int|string): mixed + * } $options Passes through the options available in {@see Pool::__construct}. * - * @return array Returns an array containing the response or an exception - * in the same order that the requests were sent. + * @return array Returns an array containing the response or rejection reason in the same order that the requests were sent. * * @throws \InvalidArgumentException if the event format is incorrect. */ - public static function batch(ClientInterface $client, $requests, array $options = []): array - { + public static function batch( + ClientInterface $client, + iterable $requests, + #[\SensitiveParameter] + array $options = [] + ): array { $res = []; self::cmpCallback($options, 'fulfilled', $res); self::cmpCallback($options, 'rejected', $res); @@ -111,15 +287,238 @@ class Pool implements PromisorInterface private static function cmpCallback(array &$options, string $name, array &$results): void { if (!isset($options[$name])) { - $options[$name] = static function ($v, $k) use (&$results) { + $options[$name] = static function ( + #[\SensitiveParameter] + $v, + $k + ) use (&$results): void { $results[$k] = $v; }; } else { $currentFn = $options[$name]; - $options[$name] = static function ($v, $k) use (&$results, $currentFn) { + $options[$name] = static function ( + #[\SensitiveParameter] + $v, + $k + ) use (&$results, $currentFn): void { $currentFn($v, $k); $results[$k] = $v; }; } } + + /** + * Returns the request options with any observer callbacks wrapped so that + * they also receive the request's iterable key as a trailing argument. + * + * @param array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface, int|string): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface, int|string): mixed, + * on_stats?: callable(TransferStats, int|string): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface, int|string): mixed, + * progress?: callable(int, int, int, int, int|string): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } $options + * @param int|string $key + * + * @return array{ + * base_uri?: string|UriInterface, + * allow_redirects?: bool|array{ + * max?: int, + * strict?: bool, + * referer?: bool, + * protocols?: non-empty-array, + * on_redirect?: callable(RequestInterface, ResponseInterface, UriInterface): mixed, + * track_redirects?: bool + * }, + * auth?: array{ + * 0: string, + * 1: string, + * 2?: string|null + * }|string|false|null, + * body?: resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable, + * cert?: string|array{ + * 0: string, + * 1?: string|null + * }, + * cert_type?: string, + * connect_timeout?: int|float, + * cookies?: false|CookieJarInterface, + * crypto_method?: int, + * crypto_method_max?: int, + * debug?: bool|resource, + * decode_content?: bool|string, + * delay?: int|float, + * expect?: bool|int, + * form_params?: array, + * force_ip_resolve?: string, + * headers?: array>|null, + * http_errors?: bool, + * idn_conversion?: bool|int|null, + * json?: mixed, + * multipart?: array, + * filename?: string + * }>, + * multiplex?: string, + * on_headers?: callable(ResponseInterface, RequestInterface): mixed, + * on_stats?: callable(TransferStats): mixed, + * on_trailers?: callable(array>, ResponseInterface, RequestInterface): mixed, + * progress?: callable(int, int, int, int): mixed, + * protocols?: non-empty-array, + * proxy?: string|array{ + * http?: string|null, + * https?: string|null, + * no?: string|array|null + * }, + * query?: array|string, + * read_timeout?: int|float, + * retries?: int, + * request_factory?: RequestFactoryInterface, + * response_factory?: ResponseFactoryInterface, + * sink?: resource|string|StreamInterface, + * ssl_key?: string|array{ + * 0: string, + * 1?: string|null + * }, + * ssl_key_type?: string, + * stream?: bool, + * stream_factory?: StreamFactoryInterface, + * stream_context?: array, + * synchronous?: bool, + * timeout?: int|float, + * uri_factory?: UriFactoryInterface, + * verify?: bool|string, + * version?: string|int|float, + * curl?: array, + * ... + * } + */ + private static function keyedRequestOptions(array $options, $key): array + { + if (\is_array($options['allow_redirects'] ?? null) + && \is_callable($options['allow_redirects']['on_redirect'] ?? null) + ) { + $onRedirect = $options['allow_redirects']['on_redirect']; + $options['allow_redirects']['on_redirect'] = static function ( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + ResponseInterface $response, + #[\SensitiveParameter] + UriInterface $uri + ) use ($onRedirect, $key): void { + $onRedirect($request, $response, $uri, $key); + }; + } + + if (\is_callable($options['on_headers'] ?? null)) { + $onHeaders = $options['on_headers']; + $options['on_headers'] = static function ( + #[\SensitiveParameter] + ResponseInterface $response, + #[\SensitiveParameter] + RequestInterface $request + ) use ($onHeaders, $key): void { + $onHeaders($response, $request, $key); + }; + } + + if (\is_callable($options['on_stats'] ?? null)) { + $onStats = $options['on_stats']; + $options['on_stats'] = static function (TransferStats $stats) use ($onStats, $key): void { + $onStats($stats, $key); + }; + } + + if (\is_callable($options['on_trailers'] ?? null)) { + $onTrailers = $options['on_trailers']; + $options['on_trailers'] = static function ( + array $trailers, + #[\SensitiveParameter] + ResponseInterface $response, + #[\SensitiveParameter] + RequestInterface $request + ) use ($onTrailers, $key): void { + $onTrailers($trailers, $response, $request, $key); + }; + } + + if (\is_callable($options['progress'] ?? null)) { + $progress = $options['progress']; + $options['progress'] = static function (int $downloadTotal, int $downloadedBytes, int $uploadTotal, int $uploadedBytes) use ($progress, $key) { + return $progress($downloadTotal, $downloadedBytes, $uploadTotal, $uploadedBytes, $key); + }; + } + + return $options; + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php index 7ca628338..d13e722ad 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -1,9 +1,13 @@ ): PromiseInterface */ private $nextHandler; /** - * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. */ public function __construct(callable $nextHandler) { $this->nextHandler = $nextHandler; } - public function __invoke(RequestInterface $request, array $options): PromiseInterface - { + /** + * @return PromiseInterface + */ + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { $fn = $this->nextHandler; + $bodySize = RequestFraming::bodySize($request); // Don't do anything if the request has no body. - if ($request->getBody()->getSize() === 0) { + if ($bodySize === 0) { return $fn($request, $options); } @@ -50,16 +64,15 @@ class PrepareBodyMiddleware if (!$request->hasHeader('Content-Length') && !$request->hasHeader('Transfer-Encoding') ) { - $size = $request->getBody()->getSize(); - if ($size !== null) { - $modify['set_headers']['Content-Length'] = $size; - } else { + if ($bodySize !== null) { + $modify['set_headers']['Content-Length'] = (string) $bodySize; + } elseif ($request->getProtocolVersion() === '1.1') { $modify['set_headers']['Transfer-Encoding'] = 'chunked'; } } // Add the expect header if needed. - $this->addExpectHeader($request, $options, $modify); + $this->addExpectHeader($request, $options, $modify, $bodySize); return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); } @@ -67,8 +80,14 @@ class PrepareBodyMiddleware /** * Add expect header */ - private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void - { + private function addExpectHeader( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + array &$modify, + ?int $bodySize + ): void { // Determine if the Expect header should be used if ($request->hasHeader('Expect')) { return; @@ -76,14 +95,15 @@ class PrepareBodyMiddleware $expect = $options['expect'] ?? null; - // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0 - if ($expect === false || $request->getProtocolVersion() < 1.1) { + // Return if disabled or not using HTTP/1.1. + if ($expect === false || '1.1' !== $request->getProtocolVersion()) { return; } // The expect header is unconditionally enabled if ($expect === true) { $modify['set_headers']['Expect'] = '100-Continue'; + return; } @@ -95,9 +115,8 @@ class PrepareBodyMiddleware // Always add if the body cannot be rewound, the size cannot be // determined, or the size is greater than the cutoff threshold $body = $request->getBody(); - $size = $body->getSize(); - if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + if ($bodySize === null || $bodySize >= (int) $expect || !$body->isSeekable()) { $modify['set_headers']['Expect'] = '100-Continue'; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ProxyOptions.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ProxyOptions.php new file mode 100644 index 000000000..53b8b9bf0 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ProxyOptions.php @@ -0,0 +1,624 @@ +getScheme()] ?? null; + if ($schemeProxy !== null && !\is_string($schemeProxy)) { + throw new InvalidArgumentException('proxy values must be strings'); + } + + // A matching "no" entry is always a final decision, even when the + // array selects no proxy for the request scheme. Without this, an + // option-level bypass could fall through to handler-level environment + // fallback and route through a proxy the user excluded. + $noProxy = isset($proxy['no']) ? self::normalizeNoProxy($proxy['no']) : []; + if ($noProxy !== [] && self::isUriInNoProxy($uri, $noProxy)) { + return ProxySelection::bypassed(); + } + + if ($schemeProxy === null) { + return ProxySelection::none(); + } + + return ProxySelection::proxy($schemeProxy); + } + + /** + * Validate a proxy URL and return its lowercased scheme. + * + * A proxy is an authority ([userinfo@]host[:port]) with an optional scheme; + * a scheme-less value is an HTTP proxy. The scheme is matched anchored at + * the start, so leading junk before it is rejected as malformed, and the + * host and port grammar is delegated to Psr7\Rfc3986. The whole string is + * validated up front so a malformed proxy fails the same way on every + * handler, but the original value is what callers pin, so no normalization + * reaches the wire. The error message never includes the proxy, which may + * carry credentials. + * + * @throws InvalidArgumentException on a malformed proxy URL + */ + public static function proxyScheme( + #[\SensitiveParameter] + string $proxy + ): string { + $parts = \explode('://', $proxy, 2); + if (\count($parts) === 1) { + $scheme = 'http'; + $authority = $proxy; + } else { + [$scheme, $authority] = $parts; + $scheme = Psr7\Utils::asciiToLower($scheme); + if ($scheme === '' || !Rfc3986::isValidScheme($scheme)) { + throw new InvalidArgumentException('Invalid proxy URL.'); + } + } + + if (!self::isValidProxyAuthority($authority)) { + throw new InvalidArgumentException('Invalid proxy URL.'); + } + + return $scheme; + } + + /** + * Whether the string is a valid proxy authority: [userinfo@]host[:port]. + * + * A single trailing slash is tolerated; anything else after the authority + * (a path, query, or fragment) is rejected. Userinfo is not policed here + * (the proxy handles its own credentials); host and port grammar are + * delegated to Psr7\Rfc3986, where a zero port is accepted and left for the + * transport to handle as it did before. + */ + private static function isValidProxyAuthority(string $authority): bool + { + // A single trailing slash is tolerated; once removed, a proxy authority + // ([userinfo@]host[:port]) cannot contain a path, query, or fragment + // delimiter anywhere — including one before a later '@'. + if (\str_ends_with($authority, '/')) { + $authority = \substr($authority, 0, \strlen($authority) - 1); + } + + if ($authority === '' || \strpbrk($authority, '/?#') !== false) { + return false; + } + + // The host cannot contain '@', so userinfo is everything before the + // last one. It is not validated here. A value that is only userinfo, + // with nothing after the last '@', has no host and is rejected. + $segments = \explode('@', $authority); + $authority = $segments[\count($segments) - 1]; + if ($authority === '') { + return false; + } + + $port = null; + if (\str_starts_with($authority, '[')) { + $parts = \explode(']', $authority, 2); + if (\count($parts) !== 2) { + return false; + } + + $host = $parts[0].']'; + $remainder = $parts[1]; + if ($remainder !== '') { + if (!\str_starts_with($remainder, ':')) { + return false; + } + + [, $port] = \explode(':', $remainder, 2); + } + } elseif (\strpos($authority, ':') !== false) { + [$host, $port] = \explode(':', $authority, 2); + } else { + $host = $authority; + } + + // A dangling "host:" carries no port, matching the previous behavior. + if ($port === '') { + $port = null; + } + + if ($host === '' || !Rfc3986::isValidHost($host)) { + return false; + } + + return $port === null || Rfc3986::isValidPort($port); + } + + /** + * Normalize a no-proxy list from request options or NO_PROXY. + * + * @param mixed $noProxy No-proxy value as passed via request transfer options. + * + * @return string[] + * + * @throws InvalidArgumentException + */ + public static function normalizeNoProxy($noProxy): array + { + if ($noProxy === null) { + return []; + } + + if (\is_string($noProxy)) { + // Entries may be separated by whitespace as well as commas, + // matching the no_proxy environment variable conventions. + $noProxy = \preg_split('/[\s,]+/', $noProxy); + + if ($noProxy === false) { + throw new \RuntimeException('Unable to split the proxy no list: '.\preg_last_error_msg()); + } + } elseif (!\is_array($noProxy)) { + throw new InvalidArgumentException('proxy no list must be null, a string, or an array of strings'); + } + + $result = []; + foreach ($noProxy as $area) { + if (!\is_string($area)) { + throw new InvalidArgumentException('proxy no list must be null, a string, or an array of strings'); + } + + $area = \trim($area, " \n\r\t\0\x0B"); + if ($area !== '') { + $result[] = $area; + } + } + + return $result; + } + + /** + * Returns true if the provided URI matches any of the no-proxy areas. + * + * @param string[] $noProxy An array of host, host-and-port, or CIDR patterns. + * + * @throws InvalidArgumentException + */ + public static function isUriInNoProxy(UriInterface $uri, array $noProxy): bool + { + self::assertNoProxyList($noProxy); + + $target = self::parseNoProxyTarget($uri); + if ($target === null) { + return false; + } + + foreach ($noProxy as $area) { + $area = \trim($area, " \n\r\t\0\x0B"); + + if ($area === '*') { + return true; + } + + $rule = self::parseNoProxyRule($area); + if ($rule !== null && self::noProxyRuleMatches($target, $rule)) { + return true; + } + } + + return false; + } + + /** + * Returns true if the provided host matches any of the no-proxy areas. + * + * This method will strip a port from the host if it is present. Domain + * patterns are matched case-insensitively. Exact IP literal patterns are + * matched by their normalized binary address, and a host or pattern + * written in the inet_aton() shorthand a transport reads as an address, + * such as 127.1 or 0x7f000001, is matched as that address. + * + * Areas are matched in the following cases: + * 1. "*" (without quotes) always matches any hosts. + * 2. An exact domain or IP literal match. + * 3. A bare domain or a leading-dot domain matches itself and its + * subdomains. e.g. 'mit.edu' and '.mit.edu' both match 'mit.edu' + * and 'foo.mit.edu'. + * 4. IP CIDR entries match IP literal hosts. e.g. '192.168.0.0/16' will + * match '192.168.1.10' and 'fd00::/8' will match '[fd00::1]'. + * + * @param string $host Host to check against the patterns. + * @param string[] $noProxy An array of host or CIDR patterns. + * + * @throws InvalidArgumentException + */ + public static function isHostInNoProxy(string $host, array $noProxy): bool + { + if ($host === '') { + throw new InvalidArgumentException('Empty host provided'); + } + + self::assertNoProxyList($noProxy); + + $target = self::parseNoProxyHostString($host); + if ($target === null) { + return false; + } + + foreach ($noProxy as $area) { + $area = \trim($area, " \n\r\t\0\x0B"); + + if ($area === '*') { + return true; + } + + $rule = self::parseNoProxyRule($area); + if ($rule !== null && self::noProxyRuleMatches($target, $rule)) { + return true; + } + } + + return false; + } + + /** + * @param array $noProxy + * + * @throws InvalidArgumentException + */ + private static function assertNoProxyList(array $noProxy): void + { + foreach ($noProxy as $area) { + if (!\is_string($area)) { + throw new InvalidArgumentException('proxy no list must be null, a string, or an array of strings'); + } + } + } + + /** + * @return array{type: string, value: string, port: int|null}|null + */ + private static function parseNoProxyTarget(UriInterface $uri): ?array + { + $host = $uri->getHost(); + if ($host === '') { + return null; + } + + return self::parseNoProxyHost($host, $uri->getPort() ?? self::getDefaultPort($uri->getScheme())); + } + + /** + * @return array{type: string, value: string, port: int|null}|null + */ + private static function parseNoProxyHostString(string $host): ?array + { + $hostAndPort = self::splitNoProxyHostAndPort($host); + if ($hostAndPort === null) { + return null; + } + + [$host] = $hostAndPort; + + return self::parseNoProxyHost($host, null); + } + + /** + * @return array{type: string, value: string, port: int|null}|array{type: string, value: string, prefix: int}|null + */ + private static function parseNoProxyRule(string $area): ?array + { + $area = \trim($area, " \n\r\t\0\x0B"); + if ($area === '' || $area === '*') { + return null; + } + + // A single leading dot is ignored: ".example.com" matches + // example.com and its subdomains exactly like a bare domain, + // consistent with every libcurl era. The strip runs before the + // CIDR check so ".10.0.0.0/8" is a live rule on every path. + if ($area[0] === '.') { + $area = \substr($area, 1); + if ($area === '') { + return null; + } + } + + if (\strpos($area, '/') !== false) { + return self::parseNoProxyCidrRule($area); + } + + $hostAndPort = self::splitNoProxyHostAndPort($area); + if ($hostAndPort === null) { + return null; + } + + [$host, $port] = $hostAndPort; + + if ($host === '*') { + return [ + 'type' => 'wildcard', + 'value' => '*', + 'port' => $port, + ]; + } + + return self::parseNoProxyHost($host, $port); + } + + /** + * @return array{type: string, value: string, port: int|null}|null + */ + private static function parseNoProxyHost(string $host, ?int $port): ?array + { + if ($host !== '' && $host[0] === '[') { + if (\substr($host, -1) !== ']') { + return null; + } + + $address = \substr($host, 1, -1); + if (!\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return null; + } + + $host = $address; + } + + $packedIp = self::packHostAddress($host); + if ($packedIp !== false) { + return [ + 'type' => 'ip', + 'value' => $packedIp, + 'port' => $port, + ]; + } + + if ($host === '' || \strpos($host, ':') !== false) { + return null; + } + + // Normalize a single DNS root dot for no-proxy domain matching. + if (\substr($host, -1) === '.') { + $host = \substr($host, 0, -1); + if ($host === '') { + return null; + } + } + + return [ + 'type' => 'domain', + 'value' => Psr7\Utils::asciiToLower($host), + 'port' => $port, + ]; + } + + /** + * @return array{0: string, 1: int|null}|null + */ + private static function splitNoProxyHostAndPort(string $area): ?array + { + if ($area !== '' && $area[0] === '[') { + $closingBracket = \strpos($area, ']'); + if ($closingBracket === false) { + return null; + } + + $host = \substr($area, 0, $closingBracket + 1); + $tail = \substr($area, $closingBracket + 1); + if ($tail === '') { + return [$host, null]; + } + + if ($tail[0] !== ':') { + return null; + } + + $port = self::parseNoProxyPort(\substr($tail, 1)); + + return $port === null ? null : [$host, $port]; + } + + if (self::packHostAddress($area) !== false) { + return [$area, null]; + } + + $colon = \strrpos($area, ':'); + if ($colon === false) { + return [$area, null]; + } + + $port = self::parseNoProxyPort(\substr($area, $colon + 1)); + if ($port === null) { + return null; + } + + return [\substr($area, 0, $colon), $port]; + } + + private static function parseNoProxyPort(string $port): ?int + { + return self::parseBoundedUnsignedInteger($port, 65535); + } + + private static function getDefaultPort(string $scheme): ?int + { + if ($scheme === 'http') { + return 80; + } + + if ($scheme === 'https') { + return 443; + } + + return null; + } + + /** + * @return array{type: string, value: string, prefix: int}|null + */ + private static function parseNoProxyCidrRule(string $area): ?array + { + $slash = \strpos($area, '/'); + if ($slash === false) { + return null; + } + + $prefix = \substr($area, $slash + 1); + + $network = \substr($area, 0, $slash); + if ($network !== '' && $network[0] === '[' && \substr($network, -1) === ']') { + $network = \substr($network, 1, -1); + } + + // A bare rule denotes a host identity, but a CIDR rule uses network + // configuration syntax, so the inet_aton() shorthand is not read as + // a network: 127/8 would name 0.0.0.127/8 rather than 127.0.0.0/8. + $network = self::packIpAddress($network); + if ($network === false) { + return null; + } + + $prefix = self::parseBoundedUnsignedInteger($prefix, \strlen($network) * 8); + if ($prefix === null) { + return null; + } + + return [ + 'type' => 'cidr', + 'value' => $network, + 'prefix' => $prefix, + ]; + } + + private static function parseBoundedUnsignedInteger(string $value, int $max): ?int + { + if ($value === '' || !\ctype_digit($value)) { + return null; + } + + $normalized = \ltrim($value, '0'); + $normalized = $normalized === '' ? '0' : $normalized; + $limit = (string) $max; + + if (\strlen($normalized) > \strlen($limit) || (\strlen($normalized) === \strlen($limit) && \strcmp($normalized, $limit) > 0)) { + return null; + } + + return (int) $normalized; + } + + /** + * @param array{type: string, value: string, port: int|null} $target + * @param array{type: string, value: string, port?: int|null, prefix?: int|null} $rule + */ + private static function noProxyRuleMatches(array $target, array $rule): bool + { + if ($rule['type'] === 'wildcard') { + return ($rule['port'] ?? null) === null || $rule['port'] === $target['port']; + } + + if ($rule['type'] === 'cidr') { + if ($target['type'] !== 'ip' || !isset($rule['prefix'])) { + return false; + } + + if (\strlen($target['value']) !== \strlen($rule['value'])) { + return false; + } + + return self::ipMatchesPrefix($target['value'], $rule['value'], $rule['prefix']); + } + + if (($rule['port'] ?? null) !== null && $rule['port'] !== $target['port']) { + return false; + } + + if ($rule['type'] !== $target['type']) { + return false; + } + + if ($rule['type'] === 'ip') { + return $rule['value'] === $target['value']; + } + + if ($target['value'] === $rule['value']) { + return true; + } + + $suffix = '.'.$rule['value']; + + return \substr($target['value'], -\strlen($suffix)) === $suffix; + } + + /** + * @return string|false + */ + private static function packIpAddress(string $ip) + { + if (\filter_var($ip, \FILTER_VALIDATE_IP)) { + return \inet_pton($ip); + } + + return false; + } + + /** + * @return string|false + */ + private static function packHostAddress(string $host) + { + $packed = self::packIpAddress($host); + if ($packed !== false) { + return $packed; + } + + // A transport reads the inet_aton() shorthand as an address too, so a + // rule and a request host that name one address have to match + // whichever spelling each of them happens to use. + return HostIdentity::numericIpv4ToBinary($host) ?? false; + } + + private static function ipMatchesPrefix(string $address, string $network, int $prefix): bool + { + $fullBytes = \intdiv($prefix, 8); + $remainingBits = $prefix % 8; + + if ($fullBytes > 0 && \substr($address, 0, $fullBytes) !== \substr($network, 0, $fullBytes)) { + return false; + } + + if ($remainingBits === 0) { + return true; + } + + $mask = (0xFF << (8 - $remainingBits)) & 0xFF; + + return (\ord($address[$fullBytes]) & $mask) === (\ord($network[$fullBytes]) & $mask); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ProxySelection.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ProxySelection.php new file mode 100644 index 000000000..4c40f3eda --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/ProxySelection.php @@ -0,0 +1,66 @@ +proxy = $proxy; + $this->bypassed = $bypassed; + $this->disabled = $disabled; + } + + public static function none(): self + { + return new self(null, false, false); + } + + public static function proxy(string $proxy): self + { + return $proxy === '' ? self::disabled() : new self($proxy, false, false); + } + + public static function bypassed(): self + { + return new self(null, true, false); + } + + public static function disabled(): self + { + return new self(null, false, true); + } + + public function getProxy(): ?string + { + return $this->proxy; + } + + public function hasProxy(): bool + { + return $this->proxy !== null; + } + + public function isBypassed(): bool + { + return $this->bypassed; + } + + public function isDisabled(): bool + { + return $this->disabled; + } + + public function shouldDisableProxy(): bool + { + return $this->bypassed || $this->disabled; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php index f67d448be..9b23a28e2 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -1,24 +1,34 @@ 5, - 'protocols' => ['http', 'https'], - 'strict' => false, - 'referer' => false, + public const DEFAULT_SETTINGS = [ + 'max' => 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, 'track_redirects' => false, ]; /** - * @var callable(RequestInterface, array): PromiseInterface + * @var callable(RequestInterface, array): PromiseInterface */ private $nextHandler; /** - * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. */ public function __construct(callable $nextHandler) { $this->nextHandler = $nextHandler; } - public function __invoke(RequestInterface $request, array $options): PromiseInterface - { + /** + * @return PromiseInterface + */ + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { $fn = $this->nextHandler; if (empty($options['allow_redirects'])) { @@ -56,12 +73,12 @@ class RedirectMiddleware } if ($options['allow_redirects'] === true) { - $options['allow_redirects'] = self::$defaultSettings; + $options['allow_redirects'] = self::DEFAULT_SETTINGS; } elseif (!\is_array($options['allow_redirects'])) { - throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + throw new InvalidArgumentException('allow_redirects must be true, false, or array'); } else { // Merge the default settings with the provided settings - $options['allow_redirects'] += self::$defaultSettings; + $options['allow_redirects'] += self::DEFAULT_SETTINGS; } if (empty($options['allow_redirects']['max'])) { @@ -69,17 +86,26 @@ class RedirectMiddleware } return $fn($request, $options) - ->then(function (ResponseInterface $response) use ($request, $options) { + ->then(function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($request, $options) { return $this->checkRedirect($request, $options, $response); }); } /** - * @return ResponseInterface|PromiseInterface + * @return ResponseInterface|PromiseInterface */ - public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) - { - if (\strpos((string) $response->getStatusCode(), '3') !== 0 + public function checkRedirect( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + ResponseInterface $response + ) { + if (!self::isRedirectStatusCode($response->getStatusCode()) || !$response->hasHeader('Location') ) { return $response; @@ -88,12 +114,16 @@ class RedirectMiddleware $this->guardMax($request, $response, $options); $nextRequest = $this->modifyRequest($request, $options, $response); - // If authorization is handled by curl, unset it if URI is cross-origin. - if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) { - unset( - $options['curl'][\CURLOPT_HTTPAUTH], - $options['curl'][\CURLOPT_USERPWD] - ); + // Remove HTTP origin credentials if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri())) { + unset($options['auth'], $options['__guzzle_digest_retries']); + + if (defined('\CURLOPT_HTTPAUTH') && defined('\CURLOPT_USERPWD')) { + unset( + $options['curl'][\CURLOPT_HTTPAUTH], + $options['curl'][\CURLOPT_USERPWD] + ); + } } if (isset($options['allow_redirects']['on_redirect'])) { @@ -104,6 +134,10 @@ class RedirectMiddleware ); } + // The caller's delay applies once, before the initial request, not + // before each followed redirect. + unset($options['delay']); + $promise = $this($nextRequest, $options); // Add headers to be able to track history of redirects. @@ -120,11 +154,22 @@ class RedirectMiddleware /** * Enable tracking on promise. + * + * @param PromiseInterface $promise + * + * @return PromiseInterface */ - private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface - { + private function withTracking( + PromiseInterface $promise, + #[\SensitiveParameter] + string $uri, + int $statusCode + ): PromiseInterface { return $promise->then( - static function (ResponseInterface $response) use ($uri, $statusCode) { + static function ( + #[\SensitiveParameter] + ResponseInterface $response + ) use ($uri, $statusCode): ResponseInterface { // Note that we are pushing to the front of the list as this // would be an earlier response than what is currently present // in the history header. @@ -144,8 +189,14 @@ class RedirectMiddleware * * @throws TooManyRedirectsException Too many redirects. */ - private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void - { + private function guardMax( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + ResponseInterface $response, + #[\SensitiveParameter] + array &$options + ): void { $current = $options['__redirect_count'] ?? 0; $options['__redirect_count'] = $current + 1; @@ -156,48 +207,67 @@ class RedirectMiddleware } } - public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface - { - // Request modifications to apply. - $modify = []; + public function modifyRequest( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + ResponseInterface $response + ): RequestInterface { + $modify = self::getRedirectRequestModifiers($request, $options, $response); $protocols = $options['allow_redirects']['protocols']; - // Use a GET request if this is an entity enclosing request and we are - // not forcing RFC compliance, but rather emulating what all browsers - // would do. - $statusCode = $response->getStatusCode(); - if ($statusCode == 303 || - ($statusCode <= 302 && !$options['allow_redirects']['strict']) - ) { - $safeMethods = ['GET', 'HEAD', 'OPTIONS']; - $requestMethod = $request->getMethod(); - - $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; - $modify['body'] = ''; + $uriFactory = $options[RequestOptions::URI_FACTORY] ?? new HttpFactory(); + if (!$uriFactory instanceof UriFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::URI_FACTORY, + UriFactoryInterface::class + )); } - $uri = self::redirectUri($request, $response, $protocols); - if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { - $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion']; - $uri = Utils::idnUriConvert($uri, $idnOptions); + $uri = self::redirectUri($uriFactory, $request, $response, $protocols); + $idnOptions = Idn::normalizeConversionOption($options['idn_conversion'] ?? null); + if ($idnOptions !== null) { + $uri = Idn::convertUri($uri, $idnOptions); } $modify['uri'] = $uri; - Psr7\Message::rewindBody($request); - // Add the Referer header if it is told to do so and only - // add the header if we are not redirecting from https to http. + // The body only needs to be rewound when the next request reuses it. + if (!isset($modify['body'])) { + try { + Psr7\Message::rewindBody($request); + } catch (\Exception $e) { + throw new ResponseException( + 'Redirect failed because the request body could not be rewound', + $request, + $response, + $e + ); + } + } + + $crossOrigin = Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri']); + + // Add a Referer only when the scheme is unchanged, and reduce it to the + // origin on cross-origin redirects so a secret-bearing path or query is + // not leaked (strict-origin-when-cross-origin, like modern browsers). if ($options['allow_redirects']['referer'] && $modify['uri']->getScheme() === $request->getUri()->getScheme() ) { - $uri = $request->getUri()->withUserInfo(''); - $modify['set_headers']['Referer'] = (string) $uri; + $referer = $request->getUri()->withUserInfo('')->withFragment(''); + if ($crossOrigin) { + $referer = $referer->withPath('/')->withQuery(''); + } + $modify['set_headers']['Referer'] = (string) $referer; } else { $modify['remove_headers'][] = 'Referer'; } // Remove Authorization and Cookie headers if URI is cross-origin. - if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + if ($crossOrigin) { $modify['remove_headers'][] = 'Authorization'; $modify['remove_headers'][] = 'Cookie'; } @@ -205,24 +275,90 @@ class RedirectMiddleware return Psr7\Utils::modifyRequest($request, $modify); } + private static function isRedirectStatusCode(int $statusCode): bool + { + return \in_array($statusCode, [301, 302, 303, 307, 308], true); + } + + /** + * @return array{ + * method?: string, + * body?: StreamInterface, + * remove_headers?: list + * } + */ + private static function getRedirectRequestModifiers( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + ResponseInterface $response + ): array { + $statusCode = $response->getStatusCode(); + if ($statusCode !== 303 + && ($statusCode > 302 || $options['allow_redirects']['strict']) + ) { + return []; + } + + $requestMethod = $request->getMethod(); + + if ($requestMethod === 'QUERY' && \in_array($statusCode, [301, 302], true)) { + return []; + } + + $streamFactory = $options[RequestOptions::STREAM_FACTORY] ?? new HttpFactory(); + if (!$streamFactory instanceof StreamFactoryInterface) { + throw new InvalidArgumentException(\sprintf( + '%s must be an instance of %s', + RequestOptions::STREAM_FACTORY, + StreamFactoryInterface::class + )); + } + + return [ + 'method' => \in_array($requestMethod, ['GET', 'HEAD', 'OPTIONS'], true) ? $requestMethod : 'GET', + 'body' => $streamFactory->createStream(''), + 'remove_headers' => ['Content-Length', 'Transfer-Encoding'], + ]; + } + /** * Set the appropriate URL on the request based on the location header. */ private static function redirectUri( + UriFactoryInterface $uriFactory, + #[\SensitiveParameter] RequestInterface $request, + #[\SensitiveParameter] ResponseInterface $response, array $protocols ): UriInterface { - $location = Psr7\UriResolver::resolve( - $request->getUri(), - new Psr7\Uri($response->getHeaderLine('Location')) - ); + $location = $response->getHeaderLine('Location'); - // Ensure that the redirect URI is allowed based on the protocols. - if (!\in_array($location->getScheme(), $protocols)) { - throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response); + try { + $locationUri = $uriFactory->createUri($location); + $resolvedUri = Psr7\UriResolver::resolve( + $request->getUri(), + $locationUri + ); + + if (!$uriFactory instanceof HttpFactory + && $locationUri->getScheme() === '' + && $locationUri->getAuthority() === '' + ) { + $resolvedUri = $uriFactory->createUri((string) $resolvedUri); + } + } catch (\InvalidArgumentException $e) { + throw new BadResponseException(\sprintf('Redirect URI, %s, is invalid.', Psr7\Utils::redactUriStringForMessage($location)), $request, $response, $e); } - return $location; + // Ensure that the redirect URI is allowed based on the protocols. + if (!\in_array($resolvedUri->getScheme(), $protocols, true)) { + throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', Psr7\Utils::redactUriForMessage($resolvedUri), \implode(', ', $protocols)), $request, $response); + } + + return $resolvedUri; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php index 20b31bc20..7fe973dbd 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -1,95 +1,169 @@ , + * default=['http', 'https']) Allowed redirect protocols. Values are + * case-sensitive; only "http" and "https" are accepted. + * - on_redirect: (callable(RequestInterface, ResponseInterface, UriInterface): mixed) + * PHP callable that is invoked when a redirect is encountered. The + * callable is invoked with the request, the redirect response that was + * received, and the effective URI. Any return value is ignored. When + * this callback is supplied via GuzzleHttp\Pool's "options" + * configuration, the pool appends the request's iterable key as a + * fourth argument. + * - track_redirects: (bool, default=false) Track redirected URI and status + * history in response headers. */ public const ALLOW_REDIRECTS = 'allow_redirects'; /** - * auth: (array) Pass an array of HTTP authentication parameters to use - * with the request. The array must contain the username in index [0], - * the password in index [1], and you can optionally provide a built-in - * authentication type in index [2]. Pass null to disable authentication - * for a request. + * auth: (array{0: string, 1: string, 2?: string|null}|string|false|null) + * Pass an array of HTTP authentication parameters to use with the request. + * The array must contain the username in index [0], the password in index + * [1], and you can optionally provide a built-in authentication type in + * index [2]. Pass false or null to disable authentication for a request. + * Built-in Basic and Digest authentication are applied by the auth + * middleware, normally from HandlerStack::create(). String values are + * passed through for custom handlers. Built-in Basic usernames must not + * contain a colon, and neither Basic usernames nor passwords may contain + * ASCII control characters. Basic passwords may contain colons. */ public const AUTH = 'auth'; /** - * body: (resource|string|null|int|float|StreamInterface|callable|\Iterator) - * Body to send in the request. + * body: (resource|string|null|StreamInterface|(callable&object)|\Iterator|\Stringable) + * Body to send in the request. Resource and stringable object values are + * converted using the configured stream_factory. Callable and iterator + * bodies use Guzzle's existing stream handling. Strings are used as literal + * body contents, even when they name a callable. Callable bodies may be + * closures or invokable objects; arrays, int, float, bool, and generic + * objects without __toString() are not valid body values. */ public const BODY = 'body'; /** - * cert: (string|array) Set to a string to specify the path to a file - * containing a PEM formatted SSL client side certificate. If a password - * is required, then set cert to an array containing the path to the PEM - * file in the first array element followed by the certificate password - * in the second array element. + * cert: (string|array{0: string, 1?: string|null}) Set to a string to + * specify the path to a client certificate file. PEM is the default + * certificate format. If a password is required, set cert to an array + * containing the certificate path in the first array element followed by + * the certificate password in the second array element. A null password is + * treated the same as omitting it. Use cert_type to specify another + * supported certificate format. + * + * @see self::CERT_TYPE */ public const CERT = 'cert'; /** - * cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false) + * cert_type: (string) Specify the SSL client certificate file type. + */ + public const CERT_TYPE = 'cert_type'; + + /** + * cookies: (false|GuzzleHttp\Cookie\CookieJarInterface, default=false) * Specifies whether or not cookies are used in a request or what cookie * jar to use or what cookies to send. This option only works if your * handler has the `cookie` middleware. Valid values are `false` and - * an instance of {@see \GuzzleHttp\Cookie\CookieJarInterface}. + * an instance of {@see Cookie\CookieJarInterface}. */ public const COOKIES = 'cookies'; /** - * connect_timeout: (float, default=0) Float describing the number of - * seconds to wait while trying to connect to a server. Use 0 to wait - * indefinitely (the default behavior). + * connect_timeout: (int|float, default=60) Number of seconds to wait + * while trying to connect to a server. Use 0 to disable the connect + * timeout. Positive values below 0.001 seconds are rejected by the + * built-in cURL handler. */ public const CONNECT_TIMEOUT = 'connect_timeout'; + /** + * crypto_method: (int) A value describing the minimum TLS protocol + * version to use. The built-in cURL and stream handlers default HTTPS + * requests to TLS 1.2 or newer. + * + * This setting must be set to one of the + * ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. cURL 7.52.0 or higher + * is required to use TLS 1.3 with the cURL handler. + */ + public const CRYPTO_METHOD = 'crypto_method'; + + /** + * crypto_method_max: (int) A value describing the maximum TLS protocol + * version to use. The built-in cURL and stream handlers default HTTPS + * requests to TLS 1.2 or newer, so a lower maximum requires explicitly + * lowering crypto_method too. + * + * This setting must be set to one of the + * ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. cURL 7.54.0 or higher + * is required in order to specify a maximum TLS version with the cURL + * handler. + */ + public const CRYPTO_METHOD_MAX = 'crypto_method_max'; + + /** + * curl: (array) Raw cURL options to apply when using a built-in cURL + * handler. + */ + public const CURL = 'curl'; + /** * debug: (bool|resource) Set to true or set to a PHP stream returned by - * fopen() enable debug output with the HTTP handler used to send a - * request. + * fopen() to enable debug output with the handler used to send a request. + * For example, when using cURL to transfer requests, cURL's verbose of + * CURLOPT_VERBOSE will be emitted. When using the PHP stream wrapper, + * stream wrapper notifications will be emitted. If set to true, the output + * is written to PHP's STDOUT. If a PHP stream is provided, output is + * written to the stream. */ public const DEBUG = 'debug'; /** - * decode_content: (bool, default=true) Specify whether or not + * decode_content: (bool|string, default=true) Specify whether or not * Content-Encoding responses (gzip, deflate, etc.) are automatically * decoded. */ public const DECODE_CONTENT = 'decode_content'; /** - * delay: (int) The amount of time to delay before sending in milliseconds. + * delay: (int|float) The finite, non-negative amount of time to delay + * before sending in milliseconds. */ public const DELAY = 'delay'; @@ -112,99 +186,248 @@ final class RequestOptions public const EXPECT = 'expect'; /** - * form_params: (array) Associative array of form field names to values - * where each value is a string or array of strings. Sets the Content-Type - * header to application/x-www-form-urlencoded when no Content-Type header - * is already present. + * form_params: (array) + * Associative array of form field names to scalar, null, or nested array + * values. Sets the Content-Type header to application/x-www-form-urlencoded + * when no Content-Type header is already present. */ public const FORM_PARAMS = 'form_params'; /** - * headers: (array) Associative array of HTTP headers. Each value MUST be - * a string or array of strings. + * headers: (array>|null) + * Associative array of HTTP headers. Each value MUST be a string or + * non-empty array of strings. */ public const HEADERS = 'headers'; /** * http_errors: (bool, default=true) Set to false to disable exceptions - * when a non- successful HTTP response is received. By default, + * when a non-successful HTTP response is received. By default, * exceptions will be thrown for 4xx and 5xx responses. This option only * works if your handler has the `httpErrors` middleware. */ public const HTTP_ERRORS = 'http_errors'; /** - * idn: (bool|int, default=true) A combination of IDNA_* constants for - * idn_to_ascii() PHP's function (see "options" parameter). Set to false to - * disable IDN support completely, or to true to use the default - * configuration (IDNA_DEFAULT constant). + * idn_conversion: (bool|int|null, default=false) A combination of IDNA_* + * constants for PHP's idn_to_ascii() function. Set to false or null to + * disable IDN support, or true to use the default configuration + * (IDNA_DEFAULT constant). */ public const IDN_CONVERSION = 'idn_conversion'; /** * json: (mixed) Adds JSON data to a request. The provided value is JSON * encoded and a Content-Type header of application/json will be added to - * the request if no Content-Type header is already present. + * the request if no Content-Type header is already present. An Accept + * header is not added automatically. */ public const JSON = 'json'; /** - * multipart: (array) Array of associative arrays, each containing a - * required "name" key mapping to the form field, name, a required - * "contents" key mapping to a StreamInterface|resource|string, an - * optional "headers" associative array of custom headers, and an - * optional "filename" key mapping to a string to send as the filename in - * the part. If no "filename" key is present, then no "filename" attribute - * will be added to the part. + * multipart: (array) Array of part arrays, each containing a required + * "name" key mapping to the string or integer form field name, a required + * "contents" key mapping to any non-array value accepted by PSR-7 + * Utils::streamFor() or a nested array of field values, an optional + * "headers" array of string custom header values, and an optional + * "filename" key mapping to a string to send as the filename in the part. + * "headers" and "filename" cannot be used when "contents" is an array. */ public const MULTIPART = 'multipart'; /** - * on_headers: (callable) A callable that is invoked when the HTTP headers - * of the response have been received but the body has not yet begun to - * download. + * multiplex: (string, default=GuzzleHttp\Multiplexing::WAIT) Controls how a + * request sent through a built-in cURL handler relates to shared, + * multiplexed connections: how an HTTP/2 or HTTP/3 request pursues one, or, + * with Multiplexing::NONE, whether the transfer may share its connection at + * all. Use Multiplexing::EAGER to avoid waiting for pending connections, + * Multiplexing::WAIT to wait on libcurl-eligible pending connections with + * CURLOPT_PIPEWAIT, normally to the same origin, + * Multiplexing::REQUIRE_EAGER to fail unless a multiplexed protocol is + * guaranteed while dialing eagerly, or Multiplexing::REQUIRE_WAIT for the + * same guarantee while also waiting on pending connections. The required + * modes require a handler that permits actual multiplexing, not merely a + * multiplexed protocol, and are rejected on a Multiplexing::NONE handler. + * The stream handler ignores EAGER and WAIT, and rejects the required + * family; CurlHandler has no multi handle to multiplex over. The required + * family also rejects final CURLOPT_HTTPAUTH masks that permit NTLM, which + * libcurl retries over HTTP/1.1. Multiplexing::NONE disables multiplexing + * for a whole handler when passed as the "multiplex" client configuration + * option, which configures the default handler and also becomes the default + * request option, or, when constructing a handler directly, as the + * CurlMultiHandler "multiplex" constructor option. A handler configured + * with Multiplexing::NONE wins over the default WAIT: default requests run + * without waiting, explicitly requested wait modes are rejected as a + * configuration conflict when the transfer would actually wait, and the + * required modes are always rejected, because they require a handler that + * permits actual multiplexing, not merely a multiplexed protocol. As a + * request option value, Multiplexing::NONE guarantees the transfer does not + * share its connection with any concurrent transfer. Multiplexing::NONE + * does not force HTTP/1.1: on a Multiplexing::NONE handler, HTTP/2 still + * negotiates and each transfer keeps its connection to itself. + * + * The request option value is accepted exactly where the guarantee holds + * and can be verified: on a CurlMultiHandler configured with + * Multiplexing::NONE, for requests whose declared protocol version is + * HTTP/1.x, on CurlHandler, and on the stream handler, which never + * multiplexes. An HTTP/2 or HTTP/3 request with a Multiplexing::NONE + * request option is rejected on a CurlMultiHandler that permits + * multiplexing. Acceptance is decided from the request's declared protocol + * version, before any transport-level downgrade: an HTTP/3 request sent + * through a proxy is delivered over HTTP/2 or HTTP/1.1 on the wire, but is + * still rejected. On a CurlMultiHandler that permits multiplexing, + * Multiplexing::NONE is also rejected with a custom "handle_factory", when + * the handler requires persistent transport sharing (the safeguards can + * require a fresh connection, which required sharing forbids), when the + * request carries an Expect: 100-continue header (its 417 retries select + * connections outside the safeguards; remove an explicitly supplied + * header, or set the "expect" request option to false to prevent it being + * added automatically), and combined with a raw CURLOPT_HTTPAUTH cURL + * option, whose authentication retries do the same. + * + * On a client whose multi handler permits multiplexing, the ordinary + * non-streaming default stack - both cURL handlers available and no + * connection caps forcing multi-only routing - runs synchronous requests on + * the CurlHandler path, which satisfies the guarantee for any protocol + * version, while asynchronous requests run on the CurlMultiHandler, so an + * HTTP/2 request with Multiplexing::NONE succeeds synchronously and is + * rejected asynchronously on the same client. Keep-alive reuse between + * consecutive transfers is unaffected, except on libcurl versions below + * 7.77.0 and from 8.11.0 through 8.12.1, where an accepted HTTP/1.x request + * on a multiplexing CurlMultiHandler forces a fresh connection. Custom + * handlers receive the "multiplex" option unchanged: its semantics are + * handler-defined, Guzzle does not guarantee it is honored, and a + * client-level Multiplexing::NONE with a custom handler flows to it as a + * default request option without client-side enforcement. + */ + public const MULTIPLEX = 'multiplex'; + + /** + * on_headers: (callable(ResponseInterface, RequestInterface): mixed) A + * callable that is invoked when the HTTP headers of the final response, or + * a 101 Switching Protocols response, have been received but the body has + * not yet begun to download. The callable is passed the response and + * request as {@see ResponseInterface} and {@see RequestInterface} objects, + * respectively. If it throws, the request promise is rejected with a + * GuzzleHttp\Exception\ResponseException (a RequestException subtype) + * wrapping the thrown exception. When this callback is supplied via + * GuzzleHttp\Pool's "options" configuration, the pool appends the request's + * iterable key as a third argument. */ public const ON_HEADERS = 'on_headers'; /** - * on_stats: (callable) allows you to get access to transfer statistics of - * a request and access the lower level transfer details of the handler - * associated with your client. ``on_stats`` is a callable that is invoked - * when a handler has finished sending a request. The callback is invoked - * with transfer statistics about the request, the response received, or - * the error encountered. Included in the data is the total amount of time - * taken to send the request. + * on_stats: (callable(TransferStats): mixed) allows you to get access to + * transfer statistics of a request and access the lower level transfer + * details of the handler associated with your client. ``on_stats`` is a + * callable that is invoked when a handler has finished sending a request. + * The callback is invoked with transfer statistics about the request, the + * response received, or the error encountered. Included in the data is the + * total amount of time taken to send the request. Exceptions thrown by + * on_stats are not wrapped by Guzzle. Built-in handlers reject non-callable + * values before starting the transfer. The built-in cURL handlers release + * native easy handles before invoking on_stats and invoke it per low-level + * transfer attempt. When this callback is supplied via GuzzleHttp\Pool's + * "options" configuration, the pool appends the request's iterable key as a + * second argument. */ public const ON_STATS = 'on_stats'; /** - * progress: (callable) Defines a function to invoke when transfer - * progress is made. The function accepts the following positional - * arguments: the total number of bytes expected to be downloaded, the - * number of bytes downloaded so far, the number of bytes expected to be - * uploaded, the number of bytes uploaded so far. + * on_trailers: (callable(array>, ResponseInterface, RequestInterface): mixed) + * A callable that is invoked exactly once when a transfer completes + * successfully, with the HTTP trailer fields of the response. The callable + * is passed an associative array of trailer field names mapped to lists of + * field values, the response, and the request. The array is empty when the + * response carried no trailer fields, and trailer field names are + * lowercased and grouped case-insensitively while values keep their wire + * order. The callable is never invoked for failed transfers. If it throws, + * the request promise is rejected with a + * GuzzleHttp\Exception\ResponseException (a RequestException subtype) + * wrapping the thrown exception. Only the built-in cURL handlers invoke + * on_trailers; the built-in stream handler rejects the option because it + * cannot observe trailer fields, and the mock handler ignores it. + * Malformed trailer field lines are discarded before parsing. Trailer + * fields are reported separately from response headers and are never + * merged into the response. When this callback is supplied via + * GuzzleHttp\Pool's "options" configuration, the pool appends the + * request's iterable key as a fourth argument. + */ + public const ON_TRAILERS = 'on_trailers'; + + /** + * progress: (callable(int, int, int, int): mixed) + * Defines a function to invoke when transfer progress is made. The function + * accepts the following positional arguments: the total number of bytes + * expected to be downloaded, the number of bytes downloaded so far, the + * number of bytes expected to be uploaded, the number of bytes uploaded so + * far. With the built-in cURL handlers, returning a truthy value aborts the + * transfer and throwing rejects the promise with a RequestException. The + * built-in stream handler ignores return values. When this callback is + * supplied via GuzzleHttp\Pool's "options" configuration, the pool appends + * the request's iterable key as a fifth argument. */ public const PROGRESS = 'progress'; /** - * proxy: (string|array) Pass a string to specify an HTTP proxy, or an - * array to specify different proxies for different protocols (where the - * key is the protocol and the value is a proxy string). + * protocols: (non-empty-array, + * default=['http', 'https']) Allowed URI schemes. Values are + * case-sensitive; only "http" and "https" are accepted. + */ + public const PROTOCOLS = 'protocols'; + + /** + * proxy: (string|array) Pass a string to specify an HTTP proxy, or an array + * to specify different proxies for different protocols (where the key is + * the protocol and the value is a proxy string or null). Provide a "no" key + * as a comma- or whitespace-delimited string, array of strings, or null to + * specify hosts, host-and-port pairs, IP literals, IP CIDR rules, or + * wildcard rules that should not be proxied. Domain rules are matched + * case-insensitively. Exact IP literals are normalized before matching, + * including the inet_aton() shorthand spellings a transport reads as an + * address. CIDR rules match IP literals only and are not port-specific. + * Custom handlers can use ProxyOptions::resolve() to apply + * Guzzle-compatible proxy selection; the built-in handlers' + * environment-variable fallback is not part of that helper. */ public const PROXY = 'proxy'; /** - * query: (array|string) Associative array of query string values to add - * to the request. This option uses PHP's http_build_query() to create - * the string representation. Pass a string value if you need more - * control than what this method provides + * query: (array|string) Associative array of query string + * values to add to the request. This option uses PHP's http_build_query() + * to create the string representation. Pass a string value if you need + * more control than what this method provides */ public const QUERY = 'query'; + /** + * request_factory: (Psr\Http\Message\RequestFactoryInterface) PSR-17 + * request factory used when creating requests through request() and + * requestAsync(). + */ + public const REQUEST_FACTORY = 'request_factory'; + + /** + * stream_factory: (Psr\Http\Message\StreamFactoryInterface) PSR-17 + * stream factory used when creating request body streams from body, + * form_params, and json request options, and when the built-in handlers + * create response body streams. + */ + public const STREAM_FACTORY = 'stream_factory'; + + /** + * response_factory: (Psr\Http\Message\ResponseFactoryInterface) PSR-17 + * response factory used by the built-in handlers when creating the + * response message. + */ + public const RESPONSE_FACTORY = 'response_factory'; + /** * sink: (resource|string|StreamInterface) Where the data of the * response is written to. Defaults to a PHP temp stream. Providing a - * string will write data to a file by the given name. + * string will write data to a file by the given name. Built-in handlers + * treat PHP resources as caller-owned; callers are responsible for closing + * resource sinks. */ public const SINK = 'sink'; @@ -217,19 +440,34 @@ final class RequestOptions public const SYNCHRONOUS = 'synchronous'; /** - * ssl_key: (array|string) Specify the path to a file containing a private - * SSL key in PEM format. If a password is required, then set to an array - * containing the path to the SSL key in the first array element followed - * by the password required for the certificate in the second element. + * ssl_key: (array{0: string, 1?: string|null}|string) Specify the path to + * a private SSL key file. PEM is the default private key format. If a + * password is required, set ssl_key to an array containing the key path in + * the first array element followed by the key password in the second + * element. A null password is treated the same as omitting it. Use + * ssl_key_type to specify another supported key format. + * + * @see self::SSL_KEY_TYPE */ public const SSL_KEY = 'ssl_key'; /** - * stream: Set to true to attempt to stream a response rather than + * ssl_key_type: (string) Specify the SSL private key file type. + */ + public const SSL_KEY_TYPE = 'ssl_key_type'; + + /** + * stream: (bool) Set to true to attempt to stream a response rather than * download it all up-front. */ public const STREAM = 'stream'; + /** + * stream_context: (array) PHP stream context options to merge into the + * context used by the built-in stream handler. + */ + public const STREAM_CONTEXT = 'stream_context'; + /** * verify: (bool|string, default=true) Describes the SSL certificate * verification behavior of a request. Set to true to enable SSL @@ -241,24 +479,48 @@ final class RequestOptions public const VERIFY = 'verify'; /** - * timeout: (float, default=0) Float describing the timeout of the - * request in seconds. Use 0 to wait indefinitely (the default behavior). + * timeout: (int|float, default=0) Number of seconds to use as the total + * timeout of the request. Use 0 to disable the total timeout (the default + * behavior). Positive values below 0.001 seconds are rejected by the + * built-in handlers. */ public const TIMEOUT = 'timeout'; /** - * read_timeout: (float, default=default_socket_timeout ini setting) Float describing - * the body read timeout, for stream requests. + * read_timeout: (int|float, default=60) Number of seconds the connection + * may sit silent at any stage of the request. Use 0 to disable the idle + * timeout. Positive values below 0.001 seconds are rejected by the + * built-in stream handler. */ public const READ_TIMEOUT = 'read_timeout'; /** - * version: (float) Specifies the HTTP protocol version to attempt to use. + * retries: (int) Current retry count used by the retry middleware. + */ + public const RETRIES = 'retries'; + + /** + * uri_factory: (Psr\Http\Message\UriFactoryInterface) PSR-17 URI factory + * used when creating URI objects from string request URI, base_uri, and + * redirect Location values. + */ + public const URI_FACTORY = 'uri_factory'; + + /** + * version: (string|int|float, default=1.1) Specifies the HTTP protocol + * version to attempt to use. + * + * Guzzle defaults to HTTP/1.1. The built-in stream handler supports + * HTTP/1.0 and HTTP/1.1. The built-in cURL handler also supports HTTP/2 + * and HTTP/3 when the installed cURL stack reports those features. For + * HTTP/2 and HTTP/3, libcurl may use a lower HTTP version when + * negotiation or connection setup falls back. */ public const VERSION = 'version'; /** - * force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol + * force_ip_resolve: ("v4"|"v6") Set to "v4" to force IPv4 resolution or + * "v6" to force IPv6 resolution when supported by the handler. */ public const FORCE_IP_RESOLVE = 'force_ip_resolve'; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php index 0236a9d54..2709653d9 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/RetryMiddleware.php @@ -1,5 +1,7 @@ ): PromiseInterface */ private $nextHandler; /** - * @var callable + * @var callable(int, RequestInterface, ResponseInterface|null, mixed): bool */ private $decider; /** - * @var callable(int) + * @var callable(int, ResponseInterface|null, RequestInterface): int */ private $delay; /** - * @param callable $decider Function that accepts the number of retries, - * a request, [response], and [exception] and - * returns true if the request is to be - * retried. - * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. - * @param (callable(int): int)|null $delay Function that accepts the number of retries - * and returns the number of - * milliseconds to delay. + * @param callable(int, RequestInterface, ResponseInterface|null, mixed): bool $decider Function that accepts the number of retries, + * a request, [response], and [rejection reason] + * and returns true if the request is to be retried. + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + * @param (callable(int, ResponseInterface|null, RequestInterface): int)|null $delay Function that returns the number of milliseconds to delay. */ - public function __construct(callable $decider, callable $nextHandler, callable $delay = null) + public function __construct(callable $decider, callable $nextHandler, ?callable $delay = null) { $this->decider = $decider; $this->nextHandler = $nextHandler; - $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + $this->delay = $delay ?: static function (int $retries): int { + return (int) ((2 ** ($retries - 1)) * 1000); + }; } /** - * Default exponential backoff delay function. - * - * @return int milliseconds. + * @return PromiseInterface */ - public static function exponentialDelay(int $retries): int - { - return (int) \pow(2, $retries - 1) * 1000; - } - - public function __invoke(RequestInterface $request, array $options): PromiseInterface - { + public function __invoke( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options + ): PromiseInterface { if (!isset($options['retries'])) { $options['retries'] = 0; + } elseif (!\is_int($options['retries'])) { + throw new \InvalidArgumentException('retries must be an integer'); } - $fn = $this->nextHandler; - return $fn($request, $options) + /** @var PromiseInterface */ + return ($this->nextHandler)($request, $options) ->then( $this->onFulfilled($request, $options), $this->onRejected($request, $options) @@ -76,7 +78,10 @@ class RetryMiddleware */ private function onFulfilled(RequestInterface $request, array $options): callable { - return function ($value) use ($request, $options) { + return function ( + #[\SensitiveParameter] + $value + ) use ($request, $options) { if (!($this->decider)( $options['retries'], $request, @@ -85,6 +90,7 @@ class RetryMiddleware )) { return $value; } + return $this->doRetry($request, $options, $value); }; } @@ -94,7 +100,10 @@ class RetryMiddleware */ private function onRejected(RequestInterface $req, array $options): callable { - return function ($reason) use ($req, $options) { + return function ( + #[\SensitiveParameter] + $reason + ) use ($req, $options): PromiseInterface { if (!($this->decider)( $options['retries'], $req, @@ -103,13 +112,25 @@ class RetryMiddleware )) { return P\Create::rejectionFor($reason); } + + /** @var PromiseInterface */ return $this->doRetry($req, $options); }; } - private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface - { - $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); + /** + * @return PromiseInterface + */ + private function doRetry( + #[\SensitiveParameter] + RequestInterface $request, + #[\SensitiveParameter] + array $options, + #[\SensitiveParameter] + ?ResponseInterface $response = null + ): PromiseInterface { + ++$options['retries']; + $options['delay'] = ($this->delay)($options['retries'], $response, $request); return $this($request, $options); } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php index 93fa334c8..c9d4b66de 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php +++ b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -1,5 +1,7 @@ ): PromiseInterface Returns the best handler for the given system. + * + * @throws \RuntimeException if no viable Handler is available. */ - public static function chooseHandler(): callable + public static function chooseHandler(array $handlerOptions = []): callable { - $handler = null; + $sharingMode = CurlShareHandleState::normalizeMode($handlerOptions['transport_sharing'] ?? null, 'transport_sharing'); + $sharingRequired = self::isTransportSharingRequired($sharingMode); + $connectionCapsRequired = self::hasConnectionCapOptions($handlerOptions); - if (\defined('CURLOPT_CUSTOMREQUEST')) { - if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) { - $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler()); - } elseif (\function_exists('curl_exec')) { - $handler = new CurlHandler(); - } elseif (\function_exists('curl_multi_exec')) { - $handler = new CurlMultiHandler(); + $sharedPoolCapsSupported = CurlVersion::supportsSharedPoolConnectionCaps(); + + if ($connectionCapsRequired && !$sharedPoolCapsSupported && $sharingMode === TransportSharing::PERSISTENT_REQUIRE) { + throw new InvalidArgumentException(\sprintf('The "max_host_connections" and "max_total_connections" options cannot be combined with required persistent transport sharing because applying connection caps to shared connection pools requires libcurl %s or higher.', CurlVersion::SHARED_POOL_CONNECTION_CAP_VERSION)); + } + + if ($connectionCapsRequired && !$sharedPoolCapsSupported && $sharingMode === TransportSharing::PERSISTENT_PREFER) { + // libcurl below 8.22.0 does not apply cURL multi connection caps to + // transfers using a shared connection pool (curl #22265), so the + // best honorable offer for preferred persistent sharing is a + // handler-lifetime share. + $sharingMode = TransportSharing::HANDLER_PREFER; + } + + $handler = self::createCurlHandler($sharingMode, $handlerOptions); + + // Handler-scoped required sharing can also be satisfied by the stream + // handler's TLS session resumption (PHP 8.6+); persistent required + // sharing is cURL-only. + $streamCanShareSessions = (bool) \ini_get('allow_url_fopen') && StreamTlsSessionCache::isSupported(); + + if ($sharingRequired && $handler === null) { + if ($sharingMode === TransportSharing::PERSISTENT_REQUIRE) { + throw new \RuntimeException('Required persistent transport sharing requires the PHP cURL extension, curl_exec() or curl_multi_exec(), and a supported libcurl version with SSL support.'); + } + + if (!$streamCanShareSessions) { + throw new \RuntimeException('Required transport sharing requires the PHP cURL extension (curl_exec()/curl_multi_exec()) with a supported libcurl version and SSL support, or PHP 8.6+ with the OpenSSL TLS session API and the allow_url_fopen ini setting.'); } } if (\ini_get('allow_url_fopen')) { - $handler = $handler - ? Proxy::wrapStreaming($handler, new StreamHandler()) - : new StreamHandler(); - } elseif (!$handler) { - throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.'); + return self::addStreamHandler($handler, $sharingMode, self::connectionCapOptions($handlerOptions)); } - return $handler; + if ($handler !== null) { + return $handler; + } + + if ($connectionCapsRequired) { + throw new \RuntimeException('Connection cap options require a cap-capable cURL multi handler or the allow_url_fopen ini setting for stream fallback.'); + } + + throw new \RuntimeException('GuzzleHttp requires a supported cURL version with SSL support, the allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + private static function isTransportSharingRequired(string $sharingMode): bool + { + return \in_array($sharingMode, [TransportSharing::HANDLER_REQUIRE, TransportSharing::PERSISTENT_REQUIRE], true); + } + + /** + * @param array{max_host_connections?: mixed, max_total_connections?: mixed} $handlerOptions + */ + private static function hasConnectionCapOptions(array $handlerOptions): bool + { + return self::connectionCapOptions($handlerOptions) !== []; + } + + /** + * @param array{max_host_connections?: mixed, max_total_connections?: mixed, multiplex?: mixed} $handlerOptions + * + * @return (callable(RequestInterface, array): PromiseInterface)|null + */ + private static function createCurlHandler(string $sharingMode, array $handlerOptions): ?callable + { + if (!CurlVersion::supportsCurlHandler()) { + return null; + } + + if ($sharingMode === TransportSharing::HANDLER_REQUIRE && !CurlShareHandleState::supportsHandlerRequireShare()) { + // Required handler sharing can also be satisfied by the stream + // handler's TLS session resumption, so a cURL install that cannot + // share is skipped instead of failing handler selection. + return null; + } + + $connectionCapOptions = self::connectionCapOptions($handlerOptions); + if ($connectionCapOptions !== [] && !\function_exists('curl_multi_exec')) { + return null; + } + + $curlHandlerOptions = self::createCurlHandlerOptions($sharingMode); + $curlMultiHandlerOptions = $curlHandlerOptions + $connectionCapOptions; + if (($handlerOptions['multiplex'] ?? null) === Multiplexing::NONE) { + // Forwarded to the CurlMultiHandler only: CurlHandler and + // StreamHandler validate known options, and both satisfy NONE + // per-request without a handler option. + $curlMultiHandlerOptions['multiplex'] = Multiplexing::NONE; + } + + if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) { + $multiHandler = new CurlMultiHandler($curlMultiHandlerOptions); + + if ($connectionCapOptions !== []) { + // Connection caps only govern transfers on the multi handle, so + // the synchronous CurlHandler fast path would escape them. + return $multiHandler; + } + + return Proxy::wrapSync($multiHandler, new CurlHandler($curlHandlerOptions)); + } + + if ($connectionCapOptions === [] && \function_exists('curl_exec')) { + return new CurlHandler($curlHandlerOptions); + } + + if (\function_exists('curl_multi_exec')) { + return new CurlMultiHandler($curlMultiHandlerOptions); + } + + return null; + } + + /** + * @return array + */ + private static function createCurlHandlerOptions(string $sharingMode): array + { + if ($sharingMode === TransportSharing::NONE) { + return []; + } + + $shareState = CurlShareHandleState::fromOption($sharingMode); + + return $shareState === null ? [] : ['transport_sharing' => $shareState]; + } + + /** + * @param array{max_host_connections?: mixed, max_total_connections?: mixed} $handlerOptions + * + * @return array{max_host_connections?: int, max_total_connections?: int} + */ + private static function connectionCapOptions(array $handlerOptions): array + { + $options = []; + foreach (['max_host_connections', 'max_total_connections'] as $capOption) { + $value = $handlerOptions[$capOption] ?? null; + if ($value === null) { + continue; + } + + if (!\is_int($value) || $value < 1) { + throw new InvalidArgumentException(\sprintf('%s must be a positive integer.', $capOption)); + } + + $options[$capOption] = $value; + } + + return $options; + } + + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler + * @param array{max_host_connections?: int, max_total_connections?: int} $connectionCapOptions + * + * @return callable(RequestInterface, array): PromiseInterface + */ + private static function addStreamHandler(?callable $handler, string $sharingMode, array $connectionCapOptions): callable + { + $streamHandler = new StreamHandler(['transport_sharing' => $sharingMode] + $connectionCapOptions); + + return $handler + ? Proxy::wrapStreaming($handler, $streamHandler) + : $streamHandler; } /** @@ -116,78 +251,6 @@ final class Utils return sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION); } - /** - * Returns the default cacert bundle for the current system. - * - * First, the openssl.cafile and curl.cainfo php.ini settings are checked. - * If those settings are not configured, then the common locations for - * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X - * and Windows are checked. If any of these file locations are found on - * disk, they will be utilized. - * - * Note: the result of this function is cached for subsequent calls. - * - * @throws \RuntimeException if no bundle can be found. - * - * @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+. - */ - public static function defaultCaBundle(): string - { - static $cached = null; - static $cafiles = [ - // Red Hat, CentOS, Fedora (provided by the ca-certificates package) - '/etc/pki/tls/certs/ca-bundle.crt', - // Ubuntu, Debian (provided by the ca-certificates package) - '/etc/ssl/certs/ca-certificates.crt', - // FreeBSD (provided by the ca_root_nss package) - '/usr/local/share/certs/ca-root-nss.crt', - // SLES 12 (provided by the ca-certificates package) - '/var/lib/ca-certificates/ca-bundle.pem', - // OS X provided by homebrew (using the default path) - '/usr/local/etc/openssl/cert.pem', - // Google app engine - '/etc/ca-certificates.crt', - // Windows? - 'C:\\windows\\system32\\curl-ca-bundle.crt', - 'C:\\windows\\curl-ca-bundle.crt', - ]; - - if ($cached) { - return $cached; - } - - if ($ca = \ini_get('openssl.cafile')) { - return $cached = $ca; - } - - if ($ca = \ini_get('curl.cainfo')) { - return $cached = $ca; - } - - foreach ($cafiles as $filename) { - if (\file_exists($filename)) { - return $cached = $filename; - } - } - - throw new \RuntimeException( - <<< EOT -No system CA bundle could be found in any of the the common system locations. -PHP versions earlier than 5.6 are not properly configured to use the system's -CA bundle by default. In order to verify peer certificates, you will need to -supply the path on disk to a certificate bundle to the 'verify' request -option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not -need a specific certificate bundle, then Mozilla provides a commonly used CA -bundle which can be downloaded here (provided by the maintainer of cURL): -https://curl.haxx.se/ca/cacert.pem. Once -you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP -ini setting to point to the path to the file, allowing you to omit the 'verify' -request option. See https://curl.haxx.se/docs/sslcerts.html for more -information. -EOT - ); - } - /** * Creates an associative array of lowercase header names to the actual * header casing. @@ -196,190 +259,39 @@ EOT { $result = []; foreach (\array_keys($headers) as $key) { - $result[\strtolower($key)] = $key; + $result[Psr7\Utils::asciiToLower((string) $key)] = $key; } return $result; } /** - * Returns true if the provided host matches any of the no proxy areas. + * @param mixed $protocols * - * This method will strip a port from the host if it is present. Each pattern - * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a - * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" == - * "baz.foo.com", but ".foo.com" != "foo.com"). - * - * Areas are matched in the following cases: - * 1. "*" (without quotes) always matches any hosts. - * 2. An exact match. - * 3. The area starts with "." and the area is the last part of the host. e.g. - * '.mit.edu' will match any host that ends with '.mit.edu'. - * - * @param string $host Host to check against the patterns. - * @param string[] $noProxyArray An array of host patterns. + * @return string[] * * @throws InvalidArgumentException */ - public static function isHostInNoProxy(string $host, array $noProxyArray): bool + public static function normalizeProtocols($protocols): array { - if (\strlen($host) === 0) { - throw new InvalidArgumentException('Empty host provided'); + if (!\is_array($protocols) || $protocols === []) { + throw new InvalidArgumentException('protocols must be a non-empty array of "http" and/or "https"'); } - // Strip port if present. - [$host] = \explode(':', $host, 2); + $normalized = []; - foreach ($noProxyArray as $area) { - // Always match on wildcards. - if ($area === '*') { - return true; + foreach ($protocols as $protocol) { + if (!\is_string($protocol)) { + throw new InvalidArgumentException('protocols must contain only strings'); } - if (empty($area)) { - // Don't match on empty values. - continue; + if ($protocol !== 'http' && $protocol !== 'https') { + throw new InvalidArgumentException('protocols may only contain "http" and "https"'); } - if ($area === $host) { - // Exact matches. - return true; - } - // Special match if the area when prefixed with ".". Remove any - // existing leading "." and add a new leading ".". - $area = '.' . \ltrim($area, '.'); - if (\substr($host, -(\strlen($area))) === $area) { - return true; - } + $normalized[$protocol] = true; } - return false; - } - - /** - * Wrapper for json_decode that throws when an error occurs. - * - * @param string $json JSON data to parse - * @param bool $assoc When true, returned objects will be converted - * into associative arrays. - * @param int $depth User specified recursion depth. - * @param int $options Bitmask of JSON decode options. - * - * @return object|array|string|int|float|bool|null - * - * @throws InvalidArgumentException if the JSON cannot be decoded. - * - * @link https://www.php.net/manual/en/function.json-decode.php - */ - public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0) - { - $data = \json_decode($json, $assoc, $depth, $options); - if (\JSON_ERROR_NONE !== \json_last_error()) { - throw new InvalidArgumentException('json_decode error: ' . \json_last_error_msg()); - } - - return $data; - } - - /** - * Wrapper for JSON encoding that throws when an error occurs. - * - * @param mixed $value The value being encoded - * @param int $options JSON encode option bitmask - * @param int $depth Set the maximum depth. Must be greater than zero. - * - * @throws InvalidArgumentException if the JSON cannot be encoded. - * - * @link https://www.php.net/manual/en/function.json-encode.php - */ - public static function jsonEncode($value, int $options = 0, int $depth = 512): string - { - $json = \json_encode($value, $options, $depth); - if (\JSON_ERROR_NONE !== \json_last_error()) { - throw new InvalidArgumentException('json_encode error: ' . \json_last_error_msg()); - } - - /** @var string */ - return $json; - } - - /** - * Wrapper for the hrtime() or microtime() functions - * (depending on the PHP version, one of the two is used) - * - * @return float UNIX timestamp - * - * @internal - */ - public static function currentTime(): float - { - return (float) \function_exists('hrtime') ? \hrtime(true) / 1e9 : \microtime(true); - } - - /** - * @throws InvalidArgumentException - * - * @internal - */ - public static function idnUriConvert(UriInterface $uri, int $options = 0): UriInterface - { - if ($uri->getHost()) { - $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); - if ($asciiHost === false) { - $errorBitSet = $info['errors'] ?? 0; - - $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { - return substr($name, 0, 11) === 'IDNA_ERROR_'; - }); - - $errors = []; - foreach ($errorConstants as $errorConstant) { - if ($errorBitSet & constant($errorConstant)) { - $errors[] = $errorConstant; - } - } - - $errorMessage = 'IDN conversion failed'; - if ($errors) { - $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')'; - } - - throw new InvalidArgumentException($errorMessage); - } - if ($uri->getHost() !== $asciiHost) { - // Replace URI only if the ASCII version is different - $uri = $uri->withHost($asciiHost); - } - } - - return $uri; - } - - /** - * @internal - */ - public static function getenv(string $name): ?string - { - if (isset($_SERVER[$name])) { - return (string) $_SERVER[$name]; - } - - if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { - return (string) $value; - } - - return null; - } - - /** - * @return string|false - */ - private static function idnToAsci(string $domain, int $options, ?array &$info = []) - { - if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { - return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); - } - - throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + return \array_keys($normalized); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/functions.php b/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/functions.php deleted file mode 100644 index a70d2cbf3..000000000 --- a/data/web/inc/lib/vendor/guzzlehttp/guzzle/src/functions.php +++ /dev/null @@ -1,167 +0,0 @@ - 0 ? 'INF' : '-INF')` otherwise. +- Never call `strtolower()`, `strtoupper()`, `strcasecmp()`, `stripos()`, or + other locale-sensitive case functions; fold ASCII case with + `strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')` + and its inverse instead. +- Changes in behavior need a `CHANGELOG.md` entry in the unreleased section of + the target branch and an `UPGRADING.md` note when the behavior differs between + major versions. + +## Documentation + +- Wrap markdown prose and PHPDoc text to 80 columns using greedy wrapping. Never + split a markdown link or an inline code span across a line break; a line that + cannot be broken may exceed the limit. Avoid em dashes. +- Keep PHPDoc and the corresponding `docs/` pages in sync: shared prose is + deliberately word-for-word identical, including boilerplate copied verbatim + between related functions, so apply the same edit to every copy. Only + formatting and linking may differ, such as a docs link becoming a PHPDoc + `@see` tag; the wording must never drift. diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/CHANGELOG.md b/data/web/inc/lib/vendor/guzzlehttp/promises/CHANGELOG.md index 253282eb1..5f77016d0 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/CHANGELOG.md +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/CHANGELOG.md @@ -1,11 +1,176 @@ # CHANGELOG + +## 3.1.0 - 2026-10-18 + +### Added + +- Added `State` promise state checks as a replacement for `Is` + +### Deprecated + +- Deprecated `Is` because PHP 8.6 deprecates `is` as a class name + + +## 3.0.2 - 2026-08-24 + +### Added + +- Added support for PHP 8.6 + + +## 3.0.1 - 2026-08-05 + +### Changed + +- Changed the default `TReason` of `FulfilledPromise` and `Create::promiseFor()` to `never` +- Changed the default `TValue` of `RejectedPromise` and `Create::rejectionFor()` to `never` + +### Fixed + +- Fixed `EachPromise` abandoning its aggregate when the pending window drains unsettled +- Fixed `EachPromise` admitting new work after its aggregate has settled + + +## 3.0.0 - 2026-07-20 + +### Added + +- Added `concurrency` config support to `Utils::all()` and `Each::of()` +- Added generic PHPDoc annotations to promise APIs and collection callbacks +- Added recursive and `concurrency` config support to `Utils::settle()` +- Allowed promises to be resolved without passing a value + +### Changed + +- Changed `Utils::inspect()` to return actual rejection reasons +- Changed `Utils::inspect()` to prefer the settled state over late wait function exceptions +- Changed late rejection callbacks to follow rejected promises +- Reject native PHP serialization of in-flight runtime objects +- Made static helper classes non-instantiable +- Require iterable inputs for promise collection helpers and `EachPromise` +- Iterate `IteratorAggregate` inputs to collection helpers instead of treating them as a single value +- Improved recursive `Utils::all()` handling of dynamically-added settled values and raw values + +### Removed + +- Dropped support for PHP 7.2 and 7.3 + + +## 2.5.1 - 2026-07-08 + +### Fixed + +- Fixed recursive `Utils::all()` rejecting generator inputs + + +## 2.5.0 - 2026-06-02 + +### Deprecated + +- Deprecated passing non-iterable inputs to promise collection helpers and `EachPromise` + + +## 2.4.1 - 2026-05-20 + +### Fixed + +- Fixed cancelling settled coroutines when no current promise remains + + +## 2.4.0 - 2026-05-20 + +### Changed + +- Empty `EachPromise` instances now resolve when the task queue runs without `wait()` + + +## 2.3.1 - 2026-05-19 + +### Fixed + +- Fixed `Utils::inspect()` returning the internal reason array instead of the `AggregateException` + + +## 2.3.0 - 2025-08-22 + +### Added + +- PHP 8.5 support + + +## 2.2.0 - 2025-03-27 + +### Fixed + +- Revert "Allow an empty EachPromise to be resolved by running the queue" + + +## 2.1.0 - 2025-03-27 + +### Added + +- Allow an empty EachPromise to be resolved by running the queue + + +## 2.0.4 - 2024-10-17 + +### Fixed + +- Once settled, don't allow further rejection of additional promises + + +## 2.0.3 - 2024-07-18 + +### Changed + +- PHP 8.4 support + + +## 2.0.2 - 2023-12-03 + +### Changed + +- Replaced `call_user_func*` with native calls + + +## 2.0.1 - 2023-08-03 + +### Changed + +- PHP 8.3 support + + +## 2.0.0 - 2023-05-21 + +### Added + +- Added PHP 7 type hints + +### Changed + +- All previously non-final non-exception classes have been marked as soft-final + +### Removed + +- Dropped PHP < 7.2 support +- All functions in the `GuzzleHttp\Promise` namespace + + +## 1.5.3 - 2023-05-21 + +### Changed + +- Removed remaining usage of deprecated functions + + ## 1.5.2 - 2022-08-07 ### Changed - Officially support PHP 8.2 + ## 1.5.1 - 2021-10-22 ### Fixed @@ -13,6 +178,7 @@ - Revert "Call handler when waiting on fulfilled/rejected Promise" - Fix pool memory leak when empty array of promises provided + ## 1.5.0 - 2021-10-07 ### Changed @@ -24,12 +190,14 @@ - Fix manually settle promises generated with `Utils::task` + ## 1.4.1 - 2021-02-18 ### Fixed - Fixed `each_limit` skipping promises and failing + ## 1.4.0 - 2020-09-30 ### Added diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/CLAUDE.md b/data/web/inc/lib/vendor/guzzlehttp/promises/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/README.md b/data/web/inc/lib/vendor/guzzlehttp/promises/README.md index 1ea667ab9..b2a58c4df 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/README.md +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/README.md @@ -1,546 +1,100 @@ # Guzzle Promises -[Promises/A+](https://promisesaplus.com/) implementation that handles promise -chaining and resolution iteratively, allowing for "infinite" promise chaining -while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) -for a general introduction to promises. +`guzzlehttp/promises` is a small promise library used by Guzzle for asynchronous +operations. It implements promise chaining, synchronous waiting, cancellation, +and helpers for working with groups of promises. -- [Features](#features) -- [Quick start](#quick-start) -- [Synchronous wait](#synchronous-wait) -- [Cancellation](#cancellation) -- [API](#api) - - [Promise](#promise) - - [FulfilledPromise](#fulfilledpromise) - - [RejectedPromise](#rejectedpromise) -- [Promise interop](#promise-interop) -- [Implementation notes](#implementation-notes) +Most application developers use this package through +[`guzzlehttp/guzzle`](https://github.com/guzzle/guzzle/blob/8.0/README.md) by +calling methods such as `requestAsync()`. Install this package directly when you +need promise composition without the full HTTP client. +## Installation -## Features +```bash +composer require guzzlehttp/promises +``` -- [Promises/A+](https://promisesaplus.com/) implementation. -- Promise resolution and chaining is handled iteratively, allowing for - "infinite" promise chaining. -- Promises have a synchronous `wait` method. -- Promises can be cancelled. -- Works with any object that has a `then` function. -- C# style async/await coroutine promises using - `GuzzleHttp\Promise\Coroutine::of()`. +## Version Guidance +| Version | Status | PHP Version | +|---------|--------------|--------------| +| 3.1 | Latest | >=7.4,<8.7 | +| 2.6 | Maintenance | >=7.2.5,<8.7 | +| 1.5 | End of Life | >=5.5,<8.3 | ## Quick Start -A *promise* represents the eventual result of an asynchronous operation. The -primary way of interacting with a promise is through its `then` method, which -registers callbacks to receive either a promise's eventual value or the reason -why the promise cannot be fulfilled. - -### Callbacks - -Callbacks are registered with the `then` method by providing an optional -`$onFulfilled` followed by an optional `$onRejected` function. - - ```php use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; $promise = new Promise(); + $promise->then( - // $onFulfilled function ($value) { - echo 'The promise was fulfilled.'; + echo 'Fulfilled: ' . $value; }, - // $onRejected function ($reason) { - echo 'The promise was rejected.'; - } -); -``` - -*Resolving* a promise means that you either fulfill a promise with a *value* or -reject a promise with a *reason*. Resolving a promise triggers callbacks -registered with the promise's `then` method. These callbacks are triggered -only once and in the order in which they were added. - -### Resolving a Promise - -Promises are fulfilled using the `resolve($value)` method. Resolving a promise -with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger -all of the onFulfilled callbacks (resolving a promise with a rejected promise -will reject the promise and trigger the `$onRejected` callbacks). - -```php -use GuzzleHttp\Promise\Promise; - -$promise = new Promise(); -$promise - ->then(function ($value) { - // Return a value and don't break the chain - return "Hello, " . $value; - }) - // This then is executed after the first then and receives the value - // returned from the first then. - ->then(function ($value) { - echo $value; - }); - -// Resolving the promise triggers the $onFulfilled callbacks and outputs -// "Hello, reader." -$promise->resolve('reader.'); -``` - -### Promise Forwarding - -Promises can be chained one after the other. Each then in the chain is a new -promise. The return value of a promise is what's forwarded to the next -promise in the chain. Returning a promise in a `then` callback will cause the -subsequent promises in the chain to only be fulfilled when the returned promise -has been fulfilled. The next promise in the chain will be invoked with the -resolved value of the promise. - -```php -use GuzzleHttp\Promise\Promise; - -$promise = new Promise(); -$nextPromise = new Promise(); - -$promise - ->then(function ($value) use ($nextPromise) { - echo $value; - return $nextPromise; - }) - ->then(function ($value) { - echo $value; - }); - -// Triggers the first callback and outputs "A" -$promise->resolve('A'); -// Triggers the second callback and outputs "B" -$nextPromise->resolve('B'); -``` - -### Promise Rejection - -When a promise is rejected, the `$onRejected` callbacks are invoked with the -rejection reason. - -```php -use GuzzleHttp\Promise\Promise; - -$promise = new Promise(); -$promise->then(null, function ($reason) { - echo $reason; -}); - -$promise->reject('Error!'); -// Outputs "Error!" -``` - -### Rejection Forwarding - -If an exception is thrown in an `$onRejected` callback, subsequent -`$onRejected` callbacks are invoked with the thrown exception as the reason. - -```php -use GuzzleHttp\Promise\Promise; - -$promise = new Promise(); -$promise->then(null, function ($reason) { - throw new Exception($reason); -})->then(null, function ($reason) { - assert($reason->getMessage() === 'Error!'); -}); - -$promise->reject('Error!'); -``` - -You can also forward a rejection down the promise chain by returning a -`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or -`$onRejected` callback. - -```php -use GuzzleHttp\Promise\Promise; -use GuzzleHttp\Promise\RejectedPromise; - -$promise = new Promise(); -$promise->then(null, function ($reason) { - return new RejectedPromise($reason); -})->then(null, function ($reason) { - assert($reason === 'Error!'); -}); - -$promise->reject('Error!'); -``` - -If an exception is not thrown in a `$onRejected` callback and the callback -does not return a rejected promise, downstream `$onFulfilled` callbacks are -invoked using the value returned from the `$onRejected` callback. - -```php -use GuzzleHttp\Promise\Promise; - -$promise = new Promise(); -$promise - ->then(null, function ($reason) { - return "It's ok"; - }) - ->then(function ($value) { - assert($value === "It's ok"); - }); - -$promise->reject('Error!'); -``` - - -## Synchronous Wait - -You can synchronously force promises to complete using a promise's `wait` -method. When creating a promise, you can provide a wait function that is used -to synchronously force a promise to complete. When a wait function is invoked -it is expected to deliver a value to the promise or reject the promise. If the -wait function does not deliver a value, then an exception is thrown. The wait -function provided to a promise constructor is invoked when the `wait` function -of the promise is called. - -```php -$promise = new Promise(function () use (&$promise) { - $promise->resolve('foo'); -}); - -// Calling wait will return the value of the promise. -echo $promise->wait(); // outputs "foo" -``` - -If an exception is encountered while invoking the wait function of a promise, -the promise is rejected with the exception and the exception is thrown. - -```php -$promise = new Promise(function () use (&$promise) { - throw new Exception('foo'); -}); - -$promise->wait(); // throws the exception. -``` - -Calling `wait` on a promise that has been fulfilled will not trigger the wait -function. It will simply return the previously resolved value. - -```php -$promise = new Promise(function () { die('this is not called!'); }); -$promise->resolve('foo'); -echo $promise->wait(); // outputs "foo" -``` - -Calling `wait` on a promise that has been rejected will throw an exception. If -the rejection reason is an instance of `\Exception` the reason is thrown. -Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason -can be obtained by calling the `getReason` method of the exception. - -```php -$promise = new Promise(); -$promise->reject('foo'); -$promise->wait(); -``` - -> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' - -### Unwrapping a Promise - -When synchronously waiting on a promise, you are joining the state of the -promise into the current state of execution (i.e., return the value of the -promise if it was fulfilled or throw an exception if it was rejected). This is -called "unwrapping" the promise. Waiting on a promise will by default unwrap -the promise state. - -You can force a promise to resolve and *not* unwrap the state of the promise -by passing `false` to the first argument of the `wait` function: - -```php -$promise = new Promise(); -$promise->reject('foo'); -// This will not throw an exception. It simply ensures the promise has -// been resolved. -$promise->wait(false); -``` - -When unwrapping a promise, the resolved value of the promise will be waited -upon until the unwrapped value is not a promise. This means that if you resolve -promise A with a promise B and unwrap promise A, the value returned by the -wait function will be the value delivered to promise B. - -**Note**: when you do not unwrap the promise, no value is returned. - - -## Cancellation - -You can cancel a promise that has not yet been fulfilled using the `cancel()` -method of a promise. When creating a promise you can provide an optional -cancel function that when invoked cancels the action of computing a resolution -of the promise. - - -## API - -### Promise - -When creating a promise object, you can provide an optional `$waitFn` and -`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is -expected to resolve the promise. `$cancelFn` is a function with no arguments -that is expected to cancel the computation of a promise. It is invoked when the -`cancel()` method of a promise is called. - -```php -use GuzzleHttp\Promise\Promise; - -$promise = new Promise( - function () use (&$promise) { - $promise->resolve('waited'); - }, - function () { - // do something that will cancel the promise computation (e.g., close - // a socket, cancel a database query, etc...) + echo 'Rejected: ' . $reason; } ); -assert('waited' === $promise->wait()); +$promise->resolve('done'); +Utils::queue()->run(); ``` -A promise has the following methods: - -- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` - - Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. - -- `otherwise(callable $onRejected) : PromiseInterface` - - Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. - -- `wait($unwrap = true) : mixed` - - Synchronously waits on the promise to complete. - - `$unwrap` controls whether or not the value of the promise is returned for a - fulfilled promise or if an exception is thrown if the promise is rejected. - This is set to `true` by default. - -- `cancel()` - - Attempts to cancel the promise if possible. The promise being cancelled and - the parent most ancestor that has not yet been resolved will also be - cancelled. Any promises waiting on the cancelled promise to resolve will also - be cancelled. - -- `getState() : string` - - Returns the state of the promise. One of `pending`, `fulfilled`, or - `rejected`. - -- `resolve($value)` - - Fulfills the promise with the given `$value`. - -- `reject($reason)` - - Rejects the promise with the given `$reason`. - - -### FulfilledPromise - -A fulfilled promise can be created to represent a promise that has been -fulfilled. +You can wait for a promise to complete synchronously: ```php -use GuzzleHttp\Promise\FulfilledPromise; - -$promise = new FulfilledPromise('value'); - -// Fulfilled callbacks are immediately invoked. -$promise->then(function ($value) { - echo $value; -}); +$value = $promise->wait(); ``` +A promise's `getState()` describes how it was settled, not the eventual +outcome: a promise that was resolved with another promise, directly or by +returning one from a `then()` handler, reports `fulfilled` while the inner +promise may still be pending, and `wait()` can still throw if the inner +promise rejects. Poll the state only on promises settled with plain values, +or call `wait()` first (see +[guzzle/promises#101](https://github.com/guzzle/promises/issues/101)). -### RejectedPromise - -A rejected promise can be created to represent a promise that has been -rejected. +When using Guzzle HTTP requests, asynchronous methods return +`GuzzleHttp\Promise\PromiseInterface` instances: ```php -use GuzzleHttp\Promise\RejectedPromise; - -$promise = new RejectedPromise('Error'); - -// Rejected callbacks are immediately invoked. -$promise->then(null, function ($reason) { - echo $reason; -}); +$promise = $client->requestAsync('GET', 'https://example.com'); +$response = $promise->wait(); ``` +## Documentation -## Promise Interoperability - -This library works with foreign promises that have a `then` method. This means -you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) -for example. When a foreign promise is returned inside of a then method -callback, promise resolution will occur recursively. - -```php -// Create a React promise -$deferred = new React\Promise\Deferred(); -$reactPromise = $deferred->promise(); - -// Create a Guzzle promise that is fulfilled with a React promise. -$guzzlePromise = new GuzzleHttp\Promise\Promise(); -$guzzlePromise->then(function ($value) use ($reactPromise) { - // Do something something with the value... - // Return the React promise - return $reactPromise; -}); -``` - -Please note that wait and cancel chaining is no longer possible when forwarding -a foreign promise. You will need to wrap a third-party promise with a Guzzle -promise in order to utilize wait and cancel functions with foreign promises. - - -### Event Loop Integration - -In order to keep the stack size constant, Guzzle promises are resolved -asynchronously using a task queue. When waiting on promises synchronously, the -task queue will be automatically run to ensure that the blocking promise and -any forwarded promises are resolved. When using promises asynchronously in an -event loop, you will need to run the task queue on each tick of the loop. If -you do not run the task queue, then promises will not be resolved. - -You can run the task queue using the `run()` method of the global task queue -instance. - -```php -// Get the global task queue -$queue = GuzzleHttp\Promise\Utils::queue(); -$queue->run(); -``` - -For example, you could use Guzzle promises with React using a periodic timer: - -```php -$loop = React\EventLoop\Factory::create(); -$loop->addPeriodicTimer(0, [$queue, 'run']); -``` - -*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? - - -## Implementation Notes - -### Promise Resolution and Chaining is Handled Iteratively - -By shuffling pending handlers from one owner to another, promises are -resolved iteratively, allowing for "infinite" then chaining. - -```php -then(function ($v) { - // The stack size remains constant (a good thing) - echo xdebug_get_stack_depth() . ', '; - return $v + 1; - }); -} - -$parent->resolve(0); -var_dump($p->wait()); // int(1000) - -``` - -When a promise is fulfilled or rejected with a non-promise value, the promise -then takes ownership of the handlers of each child promise and delivers values -down the chain without using recursion. - -When a promise is resolved with another promise, the original promise transfers -all of its pending handlers to the new promise. When the new promise is -eventually resolved, all of the pending handlers are delivered the forwarded -value. - -### A Promise is the Deferred - -Some promise libraries implement promises using a deferred object to represent -a computation and a promise object to represent the delivery of the result of -the computation. This is a nice separation of computation and delivery because -consumers of the promise cannot modify the value that will be eventually -delivered. - -One side effect of being able to implement promise resolution and chaining -iteratively is that you need to be able for one promise to reach into the state -of another promise to shuffle around ownership of handlers. In order to achieve -this without making the handlers of a promise publicly mutable, a promise is -also the deferred value, allowing promises of the same parent class to reach -into and modify the private properties of promises of the same type. While this -does allow consumers of the value to modify the resolution or rejection of the -deferred, it is a small price to pay for keeping the stack size constant. - -```php -$promise = new Promise(); -$promise->then(function ($value) { echo $value; }); -// The promise is the deferred value, so you can deliver a value to it. -$promise->resolve('foo'); -// prints "foo" -``` - - -## Upgrading from Function API - -A static API was first introduced in 1.4.0, in order to mitigate problems with -functions conflicting between global and local copies of the package. The -function API will be removed in 2.0.0. A migration table has been provided here -for your convenience: - -| Original Function | Replacement Method | -|----------------|----------------| -| `queue` | `Utils::queue` | -| `task` | `Utils::task` | -| `promise_for` | `Create::promiseFor` | -| `rejection_for` | `Create::rejectionFor` | -| `exception_for` | `Create::exceptionFor` | -| `iter_for` | `Create::iterFor` | -| `inspect` | `Utils::inspect` | -| `inspect_all` | `Utils::inspectAll` | -| `unwrap` | `Utils::unwrap` | -| `all` | `Utils::all` | -| `some` | `Utils::some` | -| `any` | `Utils::any` | -| `settle` | `Utils::settle` | -| `each` | `Each::of` | -| `each_limit` | `Each::ofLimit` | -| `each_limit_all` | `Each::ofLimitAll` | -| `!is_fulfilled` | `Is::pending` | -| `is_fulfilled` | `Is::fulfilled` | -| `is_rejected` | `Is::rejected` | -| `is_settled` | `Is::settled` | -| `coroutine` | `Coroutine::of` | - +- [Promise Quick Start](docs/promise-quick-start.md) +- [Promise API](docs/promise-api.md) +- [Promise Interoperability](docs/promise-interoperability.md) +- [Implementation Notes](docs/implementation-notes.md) +- [Upgrade Guide](UPGRADING.md) +- [Changelog](CHANGELOG.md) ## Security -If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. - +If you discover a security vulnerability within this package, please send an +email to security@tidelift.com. All security vulnerabilities will be promptly +addressed by the security maintainers listed in the +[Security Policy](https://github.com/guzzle/promises/security/policy). Please do +not disclose security-related issues publicly until a fix has been announced. ## License -Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. - +Guzzle is made available under the MIT License (MIT). Please see +[License File](LICENSE) for more information. ## For Enterprise Available as part of the Tidelift Subscription -The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) +The maintainers of Guzzle and thousands of other packages are working with +Tidelift to deliver commercial support and maintenance for the open source +dependencies you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact dependencies you +use. +[Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/UPGRADING.md b/data/web/inc/lib/vendor/guzzlehttp/promises/UPGRADING.md new file mode 100644 index 000000000..691c5882e --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/UPGRADING.md @@ -0,0 +1,279 @@ +Guzzle Promises Upgrade Guide +============================= + +2.x to 3.0 +---------- + +Guzzle Promises 3.0 is a major release that raises the minimum PHP version, +updates promise and collection helper signatures, adds generic PHPDoc types for +static analyzers, tightens collection helper inputs, improves recursive +collection behavior, and clarifies rejection inspection and late rejection +callback behavior. + +#### PHP Version and Dependencies + +Guzzle Promises 3.0 requires PHP `^7.4 || ^8.0`. Guzzle Promises 2.x supported +PHP `^7.2.5 || ^8.0`. + +If your application still supports PHP 7.2 or 7.3, continue using Guzzle +Promises 2.x until your minimum PHP version is raised. + +Guzzle Promises 3.0 has no runtime package dependencies beyond PHP. It removes +the 2.x runtime dependency on `symfony/deprecation-contracts`; require that +package directly if your application uses it. + +#### Optional Promise Resolution Values + +`PromiseInterface::resolve()` now accepts an optional value. Calling `resolve()` +without an argument fulfills the promise with `null`. + +Custom implementations of `PromiseInterface`, and subclasses that override +`resolve()` on `Promise`, `FulfilledPromise`, or `RejectedPromise`, must update +their method signature from `resolve($value): void` to +`resolve($value = null): void`. + +#### Generic PHPDoc Types + +`PromiseInterface`, `PromisorInterface`, and the built-in promise classes now +include generic PHPDoc annotations for static analysis tools. The first template +type represents the fulfillment value and the second represents the rejection +reason. This is a static-analysis-only change and does not alter runtime +behavior, but projects with stricter static analysis may see new or different +diagnostics: + +```php +use GuzzleHttp\Promise\PromiseInterface; + +/** @var PromiseInterface */ +$promise = $factory->createPromise(); + +$value = $promise->wait(); +``` + +Code that uses unparameterized promise types continues to work and is treated as +`PromiseInterface`. If your project implements promise interfaces, +extends promise classes, or has stricter static analysis, you may need to update +your PHPDoc annotations to include the generic value and reason types. The +expanded PHPDoc also preserves promise-chain fulfillment and rejection types +more precisely through `then()` and `otherwise()`, and documents collection +callbacks with value or reason, key, and aggregate-promise arguments so +callbacks may declare only the arguments they use. + +#### Collection Helper Inputs + +Promise collection helpers now require iterable inputs. Passing a single promise +or scalar value directly now throws a `TypeError`. + +Wrap single promises or values in an array before passing them to +`Create::iterFor()`, `Each::of()`, `Each::ofLimit()`, `Each::ofLimitAll()`, +`EachPromise`, or the `Utils` collection helpers. + +```php +use GuzzleHttp\Promise\Each; + +// 2.x +$promise = Each::ofLimit($singlePromise, 2); + +// 3.0 +$promise = Each::ofLimit([$singlePromise], 2); +``` + +`IteratorAggregate` inputs are now iterated via `getIterator()`. In 2.x an +aggregate was treated as a single value and produced one result; in 3.0 its +entries are consumed individually. + +```php +use GuzzleHttp\Promise\Utils; + +$aggregate = new \ArrayObject([$promiseA, $promiseB]); + +// 2.x: one result — the ArrayObject itself +// 3.0: two results — the fulfillment values of $promiseA and $promiseB +$promise = Utils::all($aggregate); +``` + +#### Collection Helper Signatures + +`Utils::all()`, `Utils::settle()`, and `Each::of()` now accept trailing optional +arguments. Direct calls using the 2.x argument lists continue to work, and the +affected helper classes are final so subclass signatures do not need to change. +Code that mirrors or reflects exact helper signatures may need to be updated. + +Pass the recursive flag before the config array when using `Utils::all()` or +`Utils::settle()`: + +```php +use GuzzleHttp\Promise\Utils; + +$promise = Utils::all($promises, false, ['concurrency' => 5]); +$promise = Utils::settle($promises, false, ['concurrency' => 5]); +``` + +Only `concurrency` is honored by these helper config arrays. Callback config +keys such as `fulfilled` and `rejected` are ignored; pass callbacks to +`Each::of()` directly or use `EachPromise`. + +#### Recursive Collection Helpers + +Existing `Utils::all($promises, true)` calls may return different results in +3.0. Recursive mode now detects dynamically-added settled promises and raw +values. In 2.x, recursive mode only checked for pending promises. + +If you previously worked around the lack of recursive `Utils::settle()` support, +you can replace that workaround with the new `$recursive` argument: + +```php +use GuzzleHttp\Promise\Utils; + +$promise = Utils::settle($promises, true); +``` + +When `$recursive` is true, collection helpers continue taking passes over the +collection until no new entries are found and no visible promises remain +pending. This is intended for rewindable mutable collections such as +`ArrayIterator`. Generators are safe to pass, but they cannot be traversed +again once consumed, so recursive mode degrades to a single pass; use a +rewindable mutable collection when recursion needs to observe added values. + +#### Promise Inspection + +`Utils::inspect()` and `Utils::inspectAll()` now return the actual rejection +reason delivered to rejection callbacks. They no longer unwrap +`RejectionException` instances to their inner reason. + +For example, a promise rejected with a `RejectionException` now inspects with +that exception as the reason: + +```php +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\RejectionException; +use GuzzleHttp\Promise\Utils; + +$reason = new RejectionException('reason'); +$result = Utils::inspect(new RejectedPromise($reason)); + +assert($result['reason'] === $reason); +``` + +Cancelled promises now inspect with a `CancellationException` reason. If you +need the string reason from a `RejectionException` or subclass, call +`getReason()` on the exception. + +`Utils::inspect()` still reports wait-function failures as rejection reasons +when the wait function does not settle the promise. If the wait function +settles the promise and then throws, `inspect()` reports the settled +fulfillment or rejection state; direct `Promise::wait()` calls continue to +throw that late exception. + +#### Late Rejection Callbacks + +Rejection callbacks registered after a promise was resolved with a rejected +promise are now invoked with the nested rejection reason. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$promise->resolve(new RejectedPromise('reason')); + +$promise->then(null, function ($reason): void { + assert($reason === 'reason'); +}); + +Utils::queue()->run(); +``` + +#### Non-instantiable Helper Classes + +Static helper classes such as `Create`, `Each`, `Is`, and `Utils` now have +private constructors. Replace any accidental instantiation with static method +calls. + +#### Native PHP Serialization of Runtime Objects + +`Promise`, `TaskQueue`, `EachPromise`, and `Coroutine` no longer support native +PHP `serialize()` or `unserialize()`. Persist application values instead of +promise runtime state. + +1.x to 2.0 +---------- + +Guzzle Promises 2.0 is a major release that removes deprecated APIs, raises the +minimum PHP version, and adds PHP 7 parameter and return types. Applications that +only use the object-oriented API should usually need small changes. Applications +that call helper functions, implement package interfaces, extend package classes, +or pass invalid argument types need closer review. + +#### PHP Version and Dependencies + +Guzzle Promises 2.0 requires PHP `^7.2.5 || ^8.0`. Guzzle Promises 1.x +supported PHP `>=5.5`. + +#### PHP 7 Type Hints and Return Types + +Type hints and return types were added wherever possible. Please make sure: + +- You pass values of the documented type when calling methods and functions. +- Classes that implement `PromiseInterface`, `PromisorInterface`, or + `TaskQueueInterface` update method signatures to remain compatible. +- Classes that extend Guzzle Promises classes update any overridden method + signatures to remain compatible. +- Code that expected package-specific exceptions for invalid argument types may + now receive PHP `TypeError` exceptions instead. + +#### Soft-Final Classes + +All previously non-final non-exception classes are now final or annotated with +`@final`. If your code extends one of these classes, replace inheritance with +composition or implement the relevant interface directly. + +#### Removed Function API + +The static API was introduced in 1.4.0 to mitigate problems with functions +conflicting between global and local copies of the package. The function API was +removed in 2.0.0, along with the Composer `files` autoload entry that loaded +`src/functions_include.php`. + +Replace namespaced function calls with the corresponding static methods in the +`GuzzleHttp\Promise` namespace: + +```php +// Before: +use function GuzzleHttp\Promise\promise_for; + +$promise = promise_for('value'); + +// After: +use GuzzleHttp\Promise\Create; + +$promise = Create::promiseFor('value'); +``` + +| Original Function | Replacement Method | +|-------------------|--------------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + +For the full 2.0 diff, see +https://github.com/guzzle/promises/compare/1.5.3...2.0.0. diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json b/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json index c959fb32b..eef7556cb 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/composer.json @@ -1,8 +1,10 @@ { "name": "guzzlehttp/promises", "description": "Guzzle promises library", - "keywords": ["promise"], "license": "MIT", + "keywords": [ + "promise" + ], "authors": [ { "name": "Graham Campbell", @@ -26,33 +28,34 @@ } ], "require": { - "php": ">=5.5" + "php": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^9.6.34" }, "autoload": { "psr-4": { "GuzzleHttp\\Promise\\": "src/" - }, - "files": ["src/functions_include.php"] + } }, "autoload-dev": { "psr-4": { "GuzzleHttp\\Promise\\Tests\\": "tests/" } }, - "scripts": { - "test": "vendor/bin/simple-phpunit", - "test-ci": "vendor/bin/simple-phpunit --coverage-text" - }, - "extra": { - "branch-alias": { - "dev-master": "1.5-dev" - } - }, "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, "preferred-install": "dist", "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/docs/implementation-notes.md b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/implementation-notes.md new file mode 100644 index 000000000..8e4071c2f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/implementation-notes.md @@ -0,0 +1,72 @@ +# Implementation Notes + +This document describes implementation details that explain observable behavior +in `guzzlehttp/promises`, especially queue-based callback execution, iterative +resolution, and why `Promise` also acts as the deferred value. Application code +usually only needs the [Promise Quick Start](promise-quick-start.md) and +[Promise API](promise-api.md). + +## Iterative Resolution and Chaining + +Promises are resolved iteratively by moving pending handlers between promises. +This keeps stack size constant even for very long `then()` chains. + +```php +then(function ($v) { + // The stack size remains constant. + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +takes ownership of each child promise's handlers and delivers values down the +chain without recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all pending handlers receive the forwarded +value. + +## A Promise Is the Deferred + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. That separation prevents consumers from modifying the value +that will eventually be delivered. + +Iterative resolution requires one promise to move handlers from another promise. +To do that without making handlers publicly mutable, `Promise` is also the +deferred value. Promises of the same class can modify each other's private +state, including handler ownership. This means a consumer that receives a +`Promise` can also resolve or reject it, but it keeps chaining efficient and +stack safe. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +GuzzleHttp\Promise\Utils::queue()->run(); +// Prints "foo" +``` + +## Related + +- [Quick Start](promise-quick-start.md) +- [Promise API](promise-api.md) +- [Promise Interoperability](promise-interoperability.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-api.md b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-api.md new file mode 100644 index 000000000..df8ab66c1 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-api.md @@ -0,0 +1,177 @@ +# Promise API + +This reference summarizes the public API provided by `guzzlehttp/promises`. +Promise APIs are documented for static analysis as +`PromiseInterface`. `TValue` is the fulfillment value type and +`TReason` is the rejection reason type. This typing is PHPDoc-only and does not +change runtime behavior. + +Callbacks registered with `then()` are queued. They are invoked when the global +task queue runs, when a returned promise is waited on, or when the queue is +drained by the default shutdown handler. + +## PromiseInterface and Promise + +`PromiseInterface` defines the common promise contract and the states +`pending`, `fulfilled`, and `rejected`. + +When creating a `Promise`, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` receives a boolean argument and is expected to resolve or +reject the promise. `$cancelFn` receives no arguments and is invoked when +`cancel()` is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function (bool $recursive) use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // Cancel the underlying operation, such as closing a socket. + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface` appends fulfillment and rejection handlers and returns a new promise resolving to the return value of the called handler. If a handler is omitted, the original fulfillment value or rejection reason is forwarded. +- `otherwise(callable $onRejected) : PromiseInterface` appends a rejection handler and returns a new promise resolving to the callback result if called, or to the original fulfillment value if the promise is fulfilled. +- `wait(bool $unwrap = true) : mixed` synchronously waits on the promise. When `$unwrap` is `true`, fulfilled values are returned and rejected reasons are thrown. When `$unwrap` is `false`, the promise is settled without returning or throwing its result. +- `cancel() : void` attempts to cancel the promise and dependent promises. +- `getState() : string` returns `pending`, `fulfilled`, or `rejected`. +- `resolve($value = null) : void` fulfills the promise with `$value`, or with `null` if no value is given. +- `reject($reason) : void` rejects the promise with `$reason`. + +## Settled Promises + +`FulfilledPromise` represents an already fulfilled promise. Fulfillment +callbacks are still queued and run when the task queue runs or the returned +promise is waited on. + +```php +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\Utils; + +$promise = new FulfilledPromise('value'); +$promise->then(function ($value) { + echo $value; +}); + +Utils::queue()->run(); +``` + +`RejectedPromise` represents an already rejected promise. Rejection callbacks +are also queued and run when the queue is drained or the returned promise is +waited on. + +```php +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\Utils; + +$promise = new RejectedPromise('Error'); +$promise->then(null, function ($reason) { + echo $reason; +}); + +Utils::queue()->run(); +``` + +## Utils + +`GuzzleHttp\Promise\Utils` provides helpers for inspecting, aggregating, and +queuing promise work. + +- `Utils::queue(?TaskQueueInterface $assign = null) : TaskQueueInterface` returns the global task queue, or assigns a replacement queue when `$assign` is provided. +- `Utils::task(callable $task) : PromiseInterface` adds a task to the global queue and returns a promise that is fulfilled or rejected with the task result. +- `Utils::inspect(PromiseInterface $promise) : array` waits for one promise to settle and returns an inspection array with `state` and either `value` or `reason`. +- `Utils::inspectAll(iterable $promises) : array` inspects each promise and returns inspection arrays keyed like the input iterable. +- `Utils::unwrap(iterable $promises) : array` waits on all promises and returns fulfilled values, throwing if any promise rejects. +- `Utils::all(iterable $promises, bool $recursive = false, array $config = []) : PromiseInterface` returns a promise fulfilled with all values, or rejected when any input rejects. +- `Utils::settle(iterable $promises, bool $recursive = false, array $config = []) : PromiseInterface` returns a promise fulfilled with inspection arrays after all inputs settle. +- `Utils::some(int $count, iterable $promises) : PromiseInterface` fulfills with the values of the first `$count` promises to fulfill, in the order they appear in the input, or rejects with `AggregateException` if too few fulfill. +- `Utils::any(iterable $promises) : PromiseInterface` fulfills with the first fulfilled value, or rejects with `AggregateException` if none fulfill. + +`Utils::all()` and `Utils::settle()` accept `['concurrency' => 5]` or +`['concurrency' => callable]` for lazy iterables. This limits how many items are +pulled from the iterable at one time; it does not throttle promises that have +already been created or started. + +```php +use GuzzleHttp\Promise\Utils; + +$promise = Utils::all($promises, false, ['concurrency' => 5]); +``` + +## Create + +`GuzzleHttp\Promise\Create` provides factories used by the promise +implementation and by callers that need to normalize values. + +- `Create::promiseFor($value) : PromiseInterface` returns `$value` when it is already a Guzzle promise, wraps foreign thenables in a Guzzle promise, or returns a fulfilled promise for plain values. +- `Create::rejectionFor($reason) : PromiseInterface` returns `$reason` when it is already a promise, or returns a rejected promise for plain reasons. +- `Create::exceptionFor($reason) : Throwable` returns throwable reasons as-is, or wraps non-throwable reasons in `RejectionException`. +- `Create::iterFor(iterable $value) : Iterator` returns an iterator for arrays, iterators, iterator aggregates, and traversables. + +## State + +`GuzzleHttp\Promise\State` provides readable state checks: + +- `State::isPending(PromiseInterface $promise) : bool` +- `State::isSettled(PromiseInterface $promise) : bool` +- `State::isFulfilled(PromiseInterface $promise) : bool` +- `State::isRejected(PromiseInterface $promise) : bool` + +`GuzzleHttp\Promise\Is` is deprecated because PHP 8.6 deprecates `is` as a class +name, and will be removed in Guzzle Promises 4.0. Replace `Is::pending()`, +`Is::settled()`, `Is::fulfilled()`, and `Is::rejected()` with the matching +`State` methods. + +## Each and EachPromise + +`Each::of()` consumes an iterable of promises or values and invokes callbacks as +items settle. Fulfillment callbacks receive the fulfilled value, iterable key, +and aggregate promise. Rejection callbacks receive the rejection reason, +iterable key, and aggregate promise. Callback return values are ignored. + +```php +use GuzzleHttp\Promise\Each; + +$promise = Each::of($promises, $onFulfilled, $onRejected, ['concurrency' => 5]); +``` + +- `Each::of(iterable $iterable, ?callable $onFulfilled = null, ?callable $onRejected = null, array $config = []) : PromiseInterface` consumes the iterable and optionally limits lazy iteration with `concurrency`. +- `Each::ofLimit(iterable $iterable, $concurrency, ?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface` is a convenience wrapper for `Each::of()` with a concurrency limit. +- `Each::ofLimitAll(iterable $iterable, $concurrency, ?callable $onFulfilled = null) : PromiseInterface` is like `ofLimit()`, but rejects the aggregate promise on the first rejection. +- `EachPromise` is the configurable class behind `Each`; use it directly when you need `fulfilled`, `rejected`, and `concurrency` keys in one configuration array. + +The concurrency options limit lazy promise creation. For HTTP request +concurrency, use `GuzzleHttp\Pool` from `guzzlehttp/guzzle`. + +## Coroutine + +`Coroutine::of(callable $generatorFn) : Coroutine` creates a promise resolved by +a generator that yields values or promises. The generator resumes with each +fulfilled value, and rejections are thrown into the generator. + +## Task Queue + +`TaskQueueInterface` exposes `isEmpty()`, `add(callable $task)`, and `run()`. +`TaskQueue` executes queued tasks in FIFO order. The default queue runs at +process shutdown unless disabled, and `wait()` drains the queue while resolving +the promise being waited on. + +## Exceptions + +- `RejectionException` is thrown by `wait()` when a rejected promise has a non-throwable reason. The original reason is available through `getReason()`. +- `AggregateException` extends `RejectionException` and is used by `Utils::some()` and `Utils::any()` when too few promises fulfill. +- `CancellationException` extends `RejectionException` and is used as the rejection reason for cancelled promises. + +## Related + +- [Quick Start](promise-quick-start.md) +- [Promise Interoperability](promise-interoperability.md) +- [Implementation Notes](implementation-notes.md) +- [Upgrade Guide](../UPGRADING.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-interoperability.md b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-interoperability.md new file mode 100644 index 000000000..85cdc159f --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-interoperability.md @@ -0,0 +1,78 @@ +# Promise Interoperability + +This guide explains how Guzzle promises interact with foreign promise +implementations. A foreign promise is any object with a `then` method, such as a +[React promise](https://github.com/reactphp/promise). When a foreign promise is +returned from a `then` callback, Guzzle forwards resolution to that promise. + +## Foreign Promises + +Capture the promise returned from `then()`. That chained promise is the Guzzle +promise that follows the foreign promise's eventual result. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; + +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +$guzzlePromise = new Promise(); +$chained = $guzzlePromise->then(function ($value) use ($reactPromise) { + // Use the Guzzle value, then continue with the React promise. + return $reactPromise; +}); + +$chained->then(function ($value) { + echo $value; +}); + +$guzzlePromise->resolve('start'); +Utils::queue()->run(); + +$deferred->resolve('done'); +Utils::queue()->run(); +``` + +Forwarding a foreign promise does not make Guzzle able to synchronously wait on +or cancel the foreign operation. The chained Guzzle promise settles only after +the foreign implementation invokes the callbacks registered through `then()`. +If the foreign promise has no compatible `wait()` or `cancel()` behavior, Guzzle +cannot invent that behavior. + +Use `Create::promiseFor($foreignPromise)` to shadow a foreign thenable as a +Guzzle promise. If the foreign object exposes `wait()` or `cancel()` methods, +the wrapper uses them. Otherwise, use the foreign implementation's event loop or +completion mechanism and drain Guzzle's task queue when callbacks need to run. + +## Event Loop Integration + +Guzzle promises use a task queue to keep stack size constant and to run promise +callbacks asynchronously. When waiting on promises synchronously, the task queue +is automatically run while resolving the blocking promise and forwarded Guzzle +promises. + +When using promises asynchronously in an event loop, run the task queue on loop +ticks. If you do not run the task queue, Guzzle promise callbacks may remain +queued. + +```php +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a short periodic +timer. Avoid zero-interval timers because they may keep the loop busy even when +there is no promise work to run. + +```php +$queue = GuzzleHttp\Promise\Utils::queue(); +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0.01, [$queue, 'run']); +``` + +## Related + +- [Quick Start](promise-quick-start.md) +- [Promise API](promise-api.md) +- [Implementation Notes](implementation-notes.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-quick-start.md b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-quick-start.md new file mode 100644 index 000000000..07a60e588 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/docs/promise-quick-start.md @@ -0,0 +1,319 @@ +# Promise Quick Start + +This guide covers the common promise operations needed when using +`guzzlehttp/promises` directly: registering callbacks, resolving or rejecting +promises, waiting synchronously, composing chains, and using generator-based +flows. For the full public surface, see the [Promise API](promise-api.md). + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either the eventual value or the reason why the +promise cannot be fulfilled. + +Guzzle promise callbacks are queued. They run when the task queue is drained, +for example by `Utils::queue()->run()`, by waiting on a returned promise, or by +the default shutdown handler at the end of the PHP process. Examples that show +callback output drain the queue explicitly. + +## Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Callbacks registered with `then` are invoked +only once and in the order in which they were added when the queue is drained. + +## Resolving a Promise + +Promises are fulfilled using the `resolve($value = null)` method. Calling +`resolve()` without an argument fulfills the promise with `null`. Resolving a +promise with any value other than a `GuzzleHttp\Promise\RejectedPromise` queues +the `$onFulfilled` callbacks. Resolving with a rejected promise rejects the +promise and queues the `$onRejected` callbacks. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$promise + ->then(function ($value) { + return 'Hello, ' . $value; + }) + ->then(function ($value) { + echo $value; + }); + +$promise->resolve('reader.'); +Utils::queue()->run(); +// Outputs "Hello, reader." +``` + +## Promise Forwarding + +Promises can be chained one after the other. Each `then` call returns a new +promise. The return value of a callback is forwarded to the next promise in the +chain. Returning a promise from a callback makes the next promise wait for that +returned promise to settle. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +$promise->resolve('A'); +Utils::queue()->run(); +// Outputs "A" + +$nextPromise->resolve('B'); +Utils::queue()->run(); +// Outputs "B" +``` + +## Promise Rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason when the queue is drained. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +Utils::queue()->run(); +// Outputs "Error!" +``` + +## Rejection Forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks receive the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +Utils::queue()->run(); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +Utils::queue()->run(); +``` + +If an exception is not thrown in an `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\Utils; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +Utils::queue()->run(); +``` + +## Synchronous Wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously complete the promise. The wait function receives a boolean +argument and is expected to resolve or reject the promise. If the wait function +does not settle the promise, an exception is thrown. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(function (bool $recursive) use (&$promise) { + $promise->resolve('foo'); +}); + +echo $promise->wait(); +// Outputs "foo" +``` + +If a throwable is encountered while invoking the wait function of a promise, +the promise is rejected with the throwable and the throwable is thrown. + +```php +$promise = new Promise(function (bool $recursive) use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // Throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function (bool $recursive) { die('this is not called!'); }); +$promise->resolve('foo'); + +echo $promise->wait(); +// Outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw. If the rejection +reason is an instance of `\Throwable`, the reason is thrown. Otherwise, a +`GuzzleHttp\Promise\RejectionException` is thrown and the reason can be +obtained by calling `getReason()` on the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with reason: foo' + +## Unwrapping a Promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current execution: the fulfilled value is returned, or the +rejection reason is thrown. This is called "unwrapping" the promise. Waiting on +a promise unwraps by default. + +You can force a promise to resolve and *not* unwrap its state by passing +`false` to `wait()`: + +```php +$promise = new Promise(); +$promise->reject('foo'); + +// This does not throw. It only ensures the promise has been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited on +until the unwrapped value is not a promise. This means that if promise A is +resolved with promise B, unwrapping promise A returns the value delivered to +promise B. + +When you do not unwrap the promise, no value is returned. + +## Inspecting a Promise + +`Utils::inspect($promise)` waits for a promise to settle and returns an array +describing its final state. For rejected promises, the `reason` entry is the +actual rejection reason delivered to rejection callbacks. + +This means `RejectionException` and subclasses are not unwrapped by +`inspect()`. For example, cancelled promises inspect with a +`CancellationException` reason. + +## Generator-Based Async + +`Coroutine::of()` creates a promise from a generator that yields values or +promises. The generator resumes each time the yielded value settles, which can +make sequential asynchronous flows easier to read. + +```php +use GuzzleHttp\Promise\Coroutine; +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = Coroutine::of(function () { + $first = yield new FulfilledPromise('A'); + $second = yield new FulfilledPromise($first . 'B'); + + yield $second . 'C'; +}); + +echo $promise->wait(); +// Outputs "ABC" +``` + +If a yielded promise rejects, the rejection is thrown into the generator. Catch +it inside the generator to recover, or let it reject the coroutine promise. + +## Cancellation + +You can cancel a promise that has not yet been fulfilled using `cancel()`. When +creating a promise, you can provide an optional cancel function that cancels the +underlying operation, such as closing a socket or aborting a query. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(null, function () { + // Cancel the underlying operation. +}); + +$promise->cancel(); +``` + +Cancellation rejects the promise with a `CancellationException` unless the +cancel function settles the promise first. + +## Related + +- [Promise API](promise-api.md) +- [Promise Interoperability](promise-interoperability.md) +- [Implementation Notes](implementation-notes.md) +- [Upgrade Guide](../UPGRADING.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php index d2b5712b9..40ffdbcf1 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/AggregateException.php @@ -1,5 +1,7 @@ then(function ($v) { echo $v; }); * - * @param callable $generatorFn Generator function to wrap into a promise. + * @template TValue = mixed + * @template TReason = mixed * - * @return Promise + * @implements PromiseInterface * - * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration */ final class Coroutine implements PromiseInterface { - /** - * @var PromiseInterface|null - */ - private $currentPromise; + use NonSerializableTrait; /** - * @var Generator + * @var PromiseInterface|null */ - private $generator; + private ?PromiseInterface $currentPromise = null; /** - * @var Promise + * @var Generator */ - private $result; + private Generator $generator; + /** + * @var Promise + */ + private PromiseInterface $result; + + /** + * @param callable(): Generator $generatorFn + */ public function __construct(callable $generatorFn) { $this->generator = $generatorFn(); - $this->result = new Promise(function () { + $this->result = new Promise(function (): void { while (isset($this->currentPromise)) { $this->currentPromise->wait(); } }); try { $this->nextCoroutine($this->generator->current()); - } catch (\Exception $exception) { - $this->result->reject($exception); - } catch (Throwable $throwable) { + } catch (\Throwable $throwable) { $this->result->reject($throwable); } } @@ -79,52 +83,76 @@ final class Coroutine implements PromiseInterface /** * Create a new coroutine. * - * @return self + * @param callable(): Generator $generatorFn + * + * @return self */ - public static function of(callable $generatorFn) + public static function of(callable $generatorFn): self { return new self($generatorFn); } + /** + * @template TFulfilledValue = never + * @template TFulfilledReason = never + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param (callable(TValue): (TFulfilledValue|PromiseInterface))|null $onFulfilled Invoked when the promise fulfills. + * @param (callable(TReason): (TRejectedValue|PromiseInterface))|null $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)> + */ public function then( - callable $onFulfilled = null, - callable $onRejected = null - ) { + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): PromiseInterface { return $this->result->then($onFulfilled, $onRejected); } - public function otherwise(callable $onRejected) + /** + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param callable(TReason): (TRejectedValue|PromiseInterface) $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface + */ + public function otherwise(callable $onRejected): PromiseInterface { return $this->result->otherwise($onRejected); } - public function wait($unwrap = true) + public function wait(bool $unwrap = true) { return $this->result->wait($unwrap); } - public function getState() + public function getState(): string { return $this->result->getState(); } - public function resolve($value) + public function resolve($value = null): void { $this->result->resolve($value); } - public function reject($reason) + public function reject($reason): void { $this->result->reject($reason); } - public function cancel() + public function cancel(): void { - $this->currentPromise->cancel(); + if (isset($this->currentPromise)) { + $this->currentPromise->cancel(); + } + $this->result->cancel(); } - private function nextCoroutine($yielded) + private function nextCoroutine($yielded): void { $this->currentPromise = Create::promiseFor($yielded) ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); @@ -133,9 +161,10 @@ final class Coroutine implements PromiseInterface /** * @internal */ - public function _handleSuccess($value) + public function _handleSuccess($value): void { - unset($this->currentPromise); + $this->currentPromise = null; + try { $next = $this->generator->send($value); if ($this->generator->valid()) { @@ -143,9 +172,7 @@ final class Coroutine implements PromiseInterface } else { $this->result->resolve($value); } - } catch (Exception $exception) { - $this->result->reject($exception); - } catch (Throwable $throwable) { + } catch (\Throwable $throwable) { $this->result->reject($throwable); } } @@ -153,16 +180,15 @@ final class Coroutine implements PromiseInterface /** * @internal */ - public function _handleFailure($reason) + public function _handleFailure($reason): void { - unset($this->currentPromise); + $this->currentPromise = null; + try { $nextYield = $this->generator->throw(Create::exceptionFor($reason)); // The throw was caught, so keep iterating on the coroutine $this->nextCoroutine($nextYield); - } catch (Exception $exception) { - $this->result->reject($exception); - } catch (Throwable $throwable) { + } catch (\Throwable $throwable) { $this->result->reject($throwable); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php index 8d038e9c1..df3b8dd5a 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Create.php @@ -1,17 +1,28 @@ = PromiseInterface * - * @return PromiseInterface + * @param TValue|TPromise $value Promise or value. + * + * @return ($value is PromiseInterface ? TPromise : FulfilledPromise) */ - public static function promiseFor($value) + public static function promiseFor($value): PromiseInterface { if ($value instanceof PromiseInterface) { return $value; @@ -23,6 +34,7 @@ final class Create $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null; $promise = new Promise($wfn, $cfn); $value->then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; } @@ -30,14 +42,18 @@ final class Create } /** - * Creates a rejected promise for a reason if the reason is not a promise. - * If the provided reason is a promise, then it is returned as-is. + * Returns `$reason` when it is already a promise, or returns a rejected + * promise for plain reasons. * - * @param mixed $reason Promise or reason. + * @template TReason + * @template TValue = never + * @template TPromise of PromiseInterface = PromiseInterface * - * @return PromiseInterface + * @param TReason|TPromise $reason Promise or reason. + * + * @return ($reason is PromiseInterface ? TPromise : RejectedPromise) */ - public static function rejectionFor($reason) + public static function rejectionFor($reason): PromiseInterface { if ($reason instanceof PromiseInterface) { return $reason; @@ -47,15 +63,16 @@ final class Create } /** - * Create an exception for a rejected promise value. + * Returns throwable reasons as-is, or wraps non-throwable reasons in + * `RejectionException`. * - * @param mixed $reason + * @template TReason * - * @return \Exception|\Throwable + * @param TReason $reason */ - public static function exceptionFor($reason) + public static function exceptionFor($reason): \Throwable { - if ($reason instanceof \Exception || $reason instanceof \Throwable) { + if ($reason instanceof \Throwable) { return $reason; } @@ -63,13 +80,17 @@ final class Create } /** - * Returns an iterator for the given value. + * Returns an iterator for arrays, iterators, iterator aggregates, and + * traversables. * - * @param mixed $value + * @template TKey of array-key + * @template TValue * - * @return \Iterator + * @param iterable $value + * + * @return \Iterator */ - public static function iterFor($value) + public static function iterFor(iterable $value): \Iterator { if ($value instanceof \Iterator) { return $value; @@ -79,6 +100,10 @@ final class Create return new \ArrayIterator($value); } - return new \ArrayIterator([$value]); + if ($value instanceof \IteratorAggregate) { + return self::iterFor($value->getIterator()); + } + + return new \IteratorIterator($value); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php index 1dda35499..0a9e50afb 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Each.php @@ -1,37 +1,63 @@ > $iterable Iterator or array to iterate over. + * @param (callable(TValue, TKey, PromiseInterface): mixed)|null $onFulfilled + * @param (callable(TReason, TKey, PromiseInterface): mixed)|null $onRejected + * @param array{concurrency?: int|(callable(int): int)} $config Configuration options. + * + * @return PromiseInterface */ public static function of( - $iterable, - callable $onFulfilled = null, - callable $onRejected = null - ) { - return (new EachPromise($iterable, [ - 'fulfilled' => $onFulfilled, - 'rejected' => $onRejected - ]))->promise(); + iterable $iterable, + ?callable $onFulfilled = null, + ?callable $onRejected = null, + array $config = [] + ): PromiseInterface { + $eachConfig = []; + + if (null !== $onFulfilled) { + $eachConfig['fulfilled'] = $onFulfilled; + } + + if (null !== $onRejected) { + $eachConfig['rejected'] = $onRejected; + } + + if (isset($config['concurrency'])) { + $eachConfig['concurrency'] = $config['concurrency']; + } + + return (new EachPromise($iterable, $eachConfig))->promise(); } /** @@ -40,49 +66,51 @@ final class Each * * $concurrency may be an integer or a function that accepts the number of * pending promises and returns a numeric concurrency limit value to allow - * for dynamic a concurrency size. + * for a dynamic concurrency size. * - * @param mixed $iterable - * @param int|callable $concurrency - * @param callable $onFulfilled - * @param callable $onRejected + * @template TKey of array-key + * @template TValue + * @template TReason * - * @return PromiseInterface + * @param iterable> $iterable + * @param int|(callable(int): int) $concurrency + * @param (callable(TValue, TKey, PromiseInterface): mixed)|null $onFulfilled + * @param (callable(TReason, TKey, PromiseInterface): mixed)|null $onRejected + * + * @return PromiseInterface */ public static function ofLimit( - $iterable, + iterable $iterable, $concurrency, - callable $onFulfilled = null, - callable $onRejected = null - ) { - return (new EachPromise($iterable, [ - 'fulfilled' => $onFulfilled, - 'rejected' => $onRejected, - 'concurrency' => $concurrency - ]))->promise(); + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): PromiseInterface { + return self::of($iterable, $onFulfilled, $onRejected, ['concurrency' => $concurrency]); } /** - * Like limit, but ensures that no promise in the given $iterable argument - * is rejected. If any promise is rejected, then the aggregate promise is - * rejected with the encountered rejection. + * Like ofLimit, but rejects the aggregate promise on the first rejection. * - * @param mixed $iterable - * @param int|callable $concurrency - * @param callable $onFulfilled + * @template TKey of array-key + * @template TValue + * @template TReason * - * @return PromiseInterface + * @param iterable> $iterable + * @param int|(callable(int): int) $concurrency + * @param (callable(TValue, TKey, PromiseInterface): mixed)|null $onFulfilled + * + * @return PromiseInterface */ public static function ofLimitAll( - $iterable, + iterable $iterable, $concurrency, - callable $onFulfilled = null - ) { - return each_limit( + ?callable $onFulfilled = null + ): PromiseInterface { + return self::ofLimit( $iterable, $concurrency, $onFulfilled, - function ($reason, $idx, PromiseInterface $aggregate) { + function ($reason, $idx, PromiseInterface $aggregate): void { $aggregate->reject($reason); } ); diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php index 280d79950..e84bd8f6b 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/EachPromise.php @@ -1,34 +1,48 @@ + * + * @final */ class EachPromise implements PromisorInterface { - private $pending = []; + use NonSerializableTrait; - private $nextPendingIndex = 0; + /** @var array>|null */ + private ?array $pending = []; - /** @var \Iterator|null */ - private $iterable; + private int $nextPendingIndex = 0; - /** @var callable|int|null */ + /** @var \Iterator>|null */ + private ?\Iterator $iterable; + + /** @var (callable(int): int)|int|null */ private $concurrency; - /** @var callable|null */ + /** @var (callable(TValue, TKey, PromiseInterface): mixed)|null */ private $onFulfilled; - /** @var callable|null */ + /** @var (callable(TReason, TKey, PromiseInterface): mixed)|null */ private $onRejected; - /** @var Promise|null */ - private $aggregate; + /** @var Promise|null */ + private ?Promise $aggregate = null; - /** @var bool|null */ - private $mutex; + private ?bool $mutex = null; + + private bool $stepWhileLocked = false; /** * Configuration hash can include the following key value pairs: @@ -48,10 +62,14 @@ class EachPromise implements PromisorInterface * allowed number of outstanding concurrently executing promises, * creating a capped pool of promises. There is no limit by default. * - * @param mixed $iterable Promises or values to iterate. - * @param array $config Configuration options + * @param iterable> $iterable Promises or values to iterate. + * @param array{ + * fulfilled?: callable(TValue, TKey, PromiseInterface): mixed, + * rejected?: callable(TReason, TKey, PromiseInterface): mixed, + * concurrency?: int|(callable(int): int) + * } $config Configuration options */ - public function __construct($iterable, array $config = []) + public function __construct(iterable $iterable, array $config = []) { $this->iterable = Create::iterFor($iterable); @@ -68,8 +86,10 @@ class EachPromise implements PromisorInterface } } - /** @psalm-suppress InvalidNullableReturnType */ - public function promise() + /** + * @return PromiseInterface + */ + public function promise(): PromiseInterface { if ($this->aggregate) { return $this->aggregate; @@ -77,43 +97,56 @@ class EachPromise implements PromisorInterface try { $this->createPromise(); - /** @psalm-assert Promise $this->aggregate */ $this->iterable->rewind(); $this->refillPending(); + if (!$this->pending) { + Utils::queue()->add(function (): void { + if (!$this->aggregate || State::isSettled($this->aggregate)) { + return; + } + + try { + $this->checkIfFinished(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } + }); + } } catch (\Throwable $e) { $this->aggregate->reject($e); - } catch (\Exception $e) { - $this->aggregate->reject($e); } - /** - * @psalm-suppress NullableReturnStatement - * @phpstan-ignore-next-line - */ return $this->aggregate; } - private function createPromise() + private function createPromise(): void { $this->mutex = false; - $this->aggregate = new Promise(function () { - if ($this->checkIfFinished()) { - return; - } - reset($this->pending); - // Consume a potentially fluctuating list of promises while - // ensuring that indexes are maintained (precluding array_shift). - while ($promise = current($this->pending)) { - next($this->pending); - $promise->wait(); - if (Is::settled($this->aggregate)) { + $this->aggregate = new Promise(function (): void { + while (true) { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (State::isSettled($this->aggregate)) { + return; + } + } + // Refill and re-sweep; give up only when nothing remains. + $this->refillPending(); + if (State::isSettled($this->aggregate) || !$this->pending) { return; } } }); // Clear the references when the promise is resolved. - $clearFn = function () { + $clearFn = function (): void { $this->iterable = $this->concurrency = $this->pending = null; $this->onFulfilled = $this->onRejected = null; $this->nextPendingIndex = 0; @@ -122,18 +155,24 @@ class EachPromise implements PromisorInterface $this->aggregate->then($clearFn, $clearFn); } - private function refillPending() + private function refillPending(): void { if (!$this->concurrency) { // Add all pending promises. - while ($this->addPending() && $this->advanceIterator()); + while ($this->addPending() && $this->advanceIterator()) { + } + return; } // Add only up to N pending promises. $concurrency = is_callable($this->concurrency) - ? call_user_func($this->concurrency, count($this->pending)) + ? ($this->concurrency)(count($this->pending)) : $this->concurrency; + // The callable can settle the aggregate; admit nothing more. + if (State::isSettled($this->aggregate)) { + return; + } $concurrency = max($concurrency - count($this->pending), 0); // Concurrency may be set to 0 to disallow new promises. if (!$concurrency) { @@ -147,10 +186,11 @@ class EachPromise implements PromisorInterface // next value to yield until promise callbacks are called. while (--$concurrency && $this->advanceIterator() - && $this->addPending()); + && $this->addPending()) { + } } - private function addPending() + private function addPending(): bool { if (!$this->iterable || !$this->iterable->valid()) { return false; @@ -164,10 +204,9 @@ class EachPromise implements PromisorInterface $idx = $this->nextPendingIndex++; $this->pending[$idx] = $promise->then( - function ($value) use ($idx, $key) { + function ($value) use ($idx, $key): void { if ($this->onFulfilled) { - call_user_func( - $this->onFulfilled, + ($this->onFulfilled)( $value, $key, $this->aggregate @@ -175,10 +214,9 @@ class EachPromise implements PromisorInterface } $this->step($idx); }, - function ($reason) use ($idx, $key) { + function ($reason) use ($idx, $key): void { if ($this->onRejected) { - call_user_func( - $this->onRejected, + ($this->onRejected)( $reason, $key, $this->aggregate @@ -191,11 +229,13 @@ class EachPromise implements PromisorInterface return true; } - private function advanceIterator() + private function advanceIterator(): bool { // Place a lock on the iterator so that we ensure to not recurse, // preventing fatal generator errors. if ($this->mutex) { + $this->stepWhileLocked = true; + return false; } @@ -204,22 +244,28 @@ class EachPromise implements PromisorInterface try { $this->iterable->next(); $this->mutex = false; - return true; } catch (\Throwable $e) { $this->aggregate->reject($e); $this->mutex = false; - return false; - } catch (\Exception $e) { - $this->aggregate->reject($e); - $this->mutex = false; + return false; } + + // Run the completion check that locked steps skipped. + if ($this->stepWhileLocked) { + $this->stepWhileLocked = false; + if (!State::isSettled($this->aggregate)) { + $this->checkIfFinished(); + } + } + + return true; } - private function step($idx) + private function step(int $idx): void { // If the promise was already resolved, then ignore this step. - if (Is::settled($this->aggregate)) { + if (State::isSettled($this->aggregate)) { return; } @@ -234,11 +280,13 @@ class EachPromise implements PromisorInterface } } - private function checkIfFinished() + /** @phpstan-impure */ + private function checkIfFinished(): bool { if (!$this->pending && !$this->iterable->valid()) { // Resolve the promise if there's nothing left to do. $this->aggregate->resolve(null); + return true; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php index 98f72a62a..83573dd1c 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -1,5 +1,7 @@ + * + * @final */ class FulfilledPromise implements PromiseInterface { + /** @var TValue */ private $value; + /** + * @param TValue $value + */ public function __construct($value) { if (is_object($value) && method_exists($value, 'then')) { @@ -23,10 +36,21 @@ class FulfilledPromise implements PromiseInterface $this->value = $value; } + /** + * @template TFulfilledValue = never + * @template TFulfilledReason = never + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param (callable(TValue): (TFulfilledValue|PromiseInterface))|null $onFulfilled Invoked when the promise fulfills. + * @param (callable(TReason): (TRejectedValue|PromiseInterface))|null $onRejected Invoked when the promise is rejected. + * + * @return ($onFulfilled is null ? self : PromiseInterface) + */ public function then( - callable $onFulfilled = null, - callable $onRejected = null - ) { + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): PromiseInterface { // Return itself if there is no onFulfilled function. if (!$onFulfilled) { return $this; @@ -35,14 +59,12 @@ class FulfilledPromise implements PromiseInterface $queue = Utils::queue(); $p = new Promise([$queue, 'run']); $value = $this->value; - $queue->add(static function () use ($p, $value, $onFulfilled) { - if (Is::pending($p)) { + $queue->add(static function () use ($p, $value, $onFulfilled): void { + if (State::isPending($p)) { try { $p->resolve($onFulfilled($value)); } catch (\Throwable $e) { $p->reject($e); - } catch (\Exception $e) { - $p->reject($e); } } }); @@ -50,34 +72,39 @@ class FulfilledPromise implements PromiseInterface return $p; } - public function otherwise(callable $onRejected) + /** + * @param callable(TReason): mixed $onRejected Invoked when the promise is rejected. + * + * @return self + */ + public function otherwise(callable $onRejected): PromiseInterface { return $this->then(null, $onRejected); } - public function wait($unwrap = true, $defaultDelivery = null) + public function wait(bool $unwrap = true) { return $unwrap ? $this->value : null; } - public function getState() + public function getState(): string { return self::FULFILLED; } - public function resolve($value) + public function resolve($value = null): void { if ($value !== $this->value) { - throw new \LogicException("Cannot resolve a fulfilled promise"); + throw new \LogicException('Cannot resolve a fulfilled promise'); } } - public function reject($reason) + public function reject($reason): void { - throw new \LogicException("Cannot reject a fulfilled promise"); + throw new \LogicException('Cannot reject a fulfilled promise'); } - public function cancel() + public function cancel(): void { // pass } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php index c3ed8d014..840d5024e 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/Is.php @@ -1,46 +1,91 @@ getState() === PromiseInterface::PENDING; + \trigger_deprecation( + 'guzzlehttp/promises', + '3.1', + '%s() is deprecated and will be removed in 4.0. Use %s::isPending() instead.', + __METHOD__, + State::class + ); + + return State::isPending($promise); } /** * Returns true if a promise is fulfilled or rejected. * - * @return bool + * @deprecated Is::settled() will be removed in guzzlehttp/promises:4.0. Use + * State::isSettled() instead. */ - public static function settled(PromiseInterface $promise) + public static function settled(PromiseInterface $promise): bool { - return $promise->getState() !== PromiseInterface::PENDING; + \trigger_deprecation( + 'guzzlehttp/promises', + '3.1', + '%s() is deprecated and will be removed in 4.0. Use %s::isSettled() instead.', + __METHOD__, + State::class + ); + + return State::isSettled($promise); } /** * Returns true if a promise is fulfilled. * - * @return bool + * @deprecated Is::fulfilled() will be removed in guzzlehttp/promises:4.0. + * Use State::isFulfilled() instead. */ - public static function fulfilled(PromiseInterface $promise) + public static function fulfilled(PromiseInterface $promise): bool { - return $promise->getState() === PromiseInterface::FULFILLED; + \trigger_deprecation( + 'guzzlehttp/promises', + '3.1', + '%s() is deprecated and will be removed in 4.0. Use %s::isFulfilled() instead.', + __METHOD__, + State::class + ); + + return State::isFulfilled($promise); } /** * Returns true if a promise is rejected. * - * @return bool + * @deprecated Is::rejected() will be removed in guzzlehttp/promises:4.0. + * Use State::isRejected() instead. */ - public static function rejected(PromiseInterface $promise) + public static function rejected(PromiseInterface $promise): bool { - return $promise->getState() === PromiseInterface::REJECTED; + \trigger_deprecation( + 'guzzlehttp/promises', + '3.1', + '%s() is deprecated and will be removed in 4.0. Use %s::isRejected() instead.', + __METHOD__, + State::class + ); + + return State::isRejected($promise); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/NonSerializableTrait.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/NonSerializableTrait.php new file mode 100644 index 000000000..c124769e4 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/NonSerializableTrait.php @@ -0,0 +1,21 @@ + + * + * @see https://promisesaplus.com/ + * + * @final */ class Promise implements PromiseInterface { - private $state = self::PENDING; + use NonSerializableTrait; + + /** @var self::PENDING|self::FULFILLED|self::REJECTED */ + private string $state = self::PENDING; + + /** @var TValue|TReason|PromiseInterface|null */ private $result; + + /** @var (callable(): void)|null */ private $cancelFn; + + /** @var (callable(bool): void)|null */ private $waitFn; - private $waitList; - private $handlers = []; + + /** @var list>|null */ + private ?array $waitList = null; + + /** @var list, 1: callable|null, 2: callable|null}>|null */ + private ?array $handlers = []; /** - * @param callable $waitFn Fn that when invoked resolves the promise. - * @param callable $cancelFn Fn that when invoked cancels the promise. + * @param (callable(bool): void)|null $waitFn Fn that when invoked resolves the promise. + * @param (callable(): void)|null $cancelFn Fn that when invoked cancels the promise. */ public function __construct( - callable $waitFn = null, - callable $cancelFn = null + ?callable $waitFn = null, + ?callable $cancelFn = null ) { $this->waitFn = $waitFn; $this->cancelFn = $cancelFn; } + /** + * @template TFulfilledValue = never + * @template TFulfilledReason = never + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param (callable(TValue): (TFulfilledValue|PromiseInterface))|null $onFulfilled Invoked when the promise fulfills. + * @param (callable(TReason): (TRejectedValue|PromiseInterface))|null $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)> + */ public function then( - callable $onFulfilled = null, - callable $onRejected = null - ) { + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): PromiseInterface { if ($this->state === self::PENDING) { $p = new Promise(null, [$this, 'cancel']); $this->handlers[] = [$p, $onFulfilled, $onRejected]; $p->waitList = $this->waitList; $p->waitList[] = $this; + return $p; } // Return a fulfilled promise and immediately invoke any callbacks. if ($this->state === self::FULFILLED) { $promise = Create::promiseFor($this->result); - return $onFulfilled ? $promise->then($onFulfilled) : $promise; + + return $promise->then($onFulfilled, $onRejected); } // It's either cancelled or rejected, so return a rejected promise // and immediately invoke any callbacks. $rejection = Create::rejectionFor($this->result); - return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + + /** @var PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)> $promise */ + $promise = $onRejected ? $rejection->then(null, $onRejected) : $rejection; + + return $promise; } - public function otherwise(callable $onRejected) + /** + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param callable(TReason): (TRejectedValue|PromiseInterface) $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface + */ + public function otherwise(callable $onRejected): PromiseInterface { return $this->then(null, $onRejected); } - public function wait($unwrap = true) + public function wait(bool $unwrap = true) { $this->waitIfPending(); @@ -71,14 +118,16 @@ class Promise implements PromiseInterface // It's rejected so "unwrap" and throw an exception. throw Create::exceptionFor($this->result); } + + return null; } - public function getState() + public function getState(): string { return $this->state; } - public function cancel() + public function cancel(): void { if ($this->state !== self::PENDING) { return; @@ -93,8 +142,6 @@ class Promise implements PromiseInterface $fn(); } catch (\Throwable $e) { $this->reject($e); - } catch (\Exception $e) { - $this->reject($e); } } @@ -105,17 +152,17 @@ class Promise implements PromiseInterface } } - public function resolve($value) + public function resolve($value = null): void { $this->settle(self::FULFILLED, $value); } - public function reject($reason) + public function reject($reason): void { $this->settle(self::REJECTED, $reason); } - private function settle($state, $value) + private function settle(string $state, $value): void { if ($this->state !== self::PENDING) { // Ignore calls with the same resolution. @@ -148,23 +195,23 @@ class Promise implements PromiseInterface if (!is_object($value) || !method_exists($value, 'then')) { $id = $state === self::FULFILLED ? 1 : 2; // It's a success, so resolve the handlers in the queue. - Utils::queue()->add(static function () use ($id, $value, $handlers) { + Utils::queue()->add(static function () use ($id, $value, $handlers): void { foreach ($handlers as $handler) { self::callHandler($id, $value, $handler); } }); - } elseif ($value instanceof Promise && Is::pending($value)) { + } elseif ($value instanceof Promise && State::isPending($value)) { // We can just merge our handlers onto the next promise. $value->handlers = array_merge($value->handlers, $handlers); } else { // Resolve the handlers when the forwarded promise is resolved. $value->then( - static function ($value) use ($handlers) { + static function ($value) use ($handlers): void { foreach ($handlers as $handler) { self::callHandler(1, $value, $handler); } }, - static function ($reason) use ($handlers) { + static function ($reason) use ($handlers): void { foreach ($handlers as $handler) { self::callHandler(2, $reason, $handler); } @@ -180,14 +227,14 @@ class Promise implements PromiseInterface * @param mixed $value Value to pass to the callback. * @param array $handler Array of handler data (promise and callbacks). */ - private static function callHandler($index, $value, array $handler) + private static function callHandler(int $index, $value, array $handler): void { - /** @var PromiseInterface $promise */ + /** @var PromiseInterface $promise */ $promise = $handler[0]; // The promise may have been cancelled or resolved before placing // this thunk in the queue. - if (Is::settled($promise)) { + if (State::isSettled($promise)) { return; } @@ -211,12 +258,10 @@ class Promise implements PromiseInterface } } catch (\Throwable $reason) { $promise->reject($reason); - } catch (\Exception $reason) { - $promise->reject($reason); } } - private function waitIfPending() + private function waitIfPending(): void { if ($this->state !== self::PENDING) { return; @@ -227,9 +272,9 @@ class Promise implements PromiseInterface } else { // If there's no wait function, then reject the promise. $this->reject('Cannot wait on a promise that has ' - . 'no internal wait function. You must provide a wait ' - . 'function when constructing the promise to be able to ' - . 'wait on a promise.'); + .'no internal wait function. You must provide a wait ' + .'function when constructing the promise to be able to ' + .'wait on a promise.'); } Utils::queue()->run(); @@ -240,13 +285,13 @@ class Promise implements PromiseInterface } } - private function invokeWaitFn() + private function invokeWaitFn(): void { try { $wfn = $this->waitFn; $this->waitFn = null; $wfn(true); - } catch (\Exception $reason) { + } catch (\Throwable $reason) { if ($this->state === self::PENDING) { // The promise has not been resolved yet, so reject the promise // with the exception. @@ -259,7 +304,7 @@ class Promise implements PromiseInterface } } - private function invokeWaitList() + private function invokeWaitList(): void { $waitList = $this->waitList; $this->waitList = null; diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php index e59833143..8b97e3bb7 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -1,5 +1,7 @@ ))|null $onFulfilled Invoked when the promise fulfills. + * @param (callable(TReason): (TRejectedValue|PromiseInterface))|null $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)> */ public function then( - callable $onFulfilled = null, - callable $onRejected = null - ); + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): PromiseInterface; /** * Appends a rejection handler callback to the promise, and returns a new @@ -37,11 +47,14 @@ interface PromiseInterface * or to its original fulfillment value if the promise is instead * fulfilled. * - * @param callable $onRejected Invoked when the promise is rejected. + * @template TRejectedValue = never + * @template TRejectedReason = never * - * @return PromiseInterface + * @param callable(TReason): (TRejectedValue|PromiseInterface) $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface */ - public function otherwise(callable $onRejected); + public function otherwise(callable $onRejected): PromiseInterface; /** * Get the state of the promise ("pending", "rejected", or "fulfilled"). @@ -49,34 +62,45 @@ interface PromiseInterface * The three states can be checked against the constants defined on * PromiseInterface: PENDING, FULFILLED, and REJECTED. * - * @return string + * The state describes how this promise was settled, not the eventual + * outcome: a promise that was resolved with another promise, directly or + * by returning one from a then() handler, reports FULFILLED while the + * inner promise may still be pending, and wait() can still throw if the + * inner promise rejects. Poll the state only on promises settled with + * plain values, or call wait() first. + * + * @return self::PENDING|self::FULFILLED|self::REJECTED + * + * @see https://github.com/guzzle/promises/issues/101 */ - public function getState(); + public function getState(): string; /** - * Resolve the promise with the given value. + * Resolve the promise with the given value, or with null if no value is given. * - * @param mixed $value + * @param TValue|PromiseInterface|null $value * - * @throws \RuntimeException if the promise is already resolved. + * @throws \LogicException if the promise is already settled with a + * conflicting resolution. */ - public function resolve($value); + public function resolve($value = null): void; /** * Reject the promise with the given reason. * - * @param mixed $reason + * @param TReason $reason * - * @throws \RuntimeException if the promise is already resolved. + * @throws \LogicException if the promise is already settled with a + * conflicting resolution. */ - public function reject($reason); + public function reject($reason): void; /** * Cancels the promise if possible. * - * @link https://github.com/promises-aplus/cancellation-spec/issues/7 + * @see https://github.com/promises-aplus/cancellation-spec/issues/7 */ - public function cancel(); + public function cancel(): void; /** * Waits until the promise completes if possible. @@ -86,12 +110,10 @@ interface PromiseInterface * * If the promise cannot be waited on, then the promise will be rejected. * - * @param bool $unwrap - * - * @return mixed + * @return ($unwrap is true ? TValue : null) * * @throws \LogicException if the promise has no wait function or if the * promise does not settle after waiting. */ - public function wait($unwrap = true); + public function wait(bool $unwrap = true); } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromisorInterface.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromisorInterface.php index 2d2e3422b..6ed6f1b6f 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromisorInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/PromisorInterface.php @@ -1,16 +1,21 @@ */ - public function promise(); + public function promise(): PromiseInterface; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectedPromise.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectedPromise.php index d2918468c..1982f28d8 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectedPromise.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectedPromise.php @@ -1,5 +1,7 @@ + * + * @final */ class RejectedPromise implements PromiseInterface { + /** @var TReason */ private $reason; + /** + * @param TReason $reason + */ public function __construct($reason) { if (is_object($reason) && method_exists($reason, 'then')) { @@ -23,10 +36,21 @@ class RejectedPromise implements PromiseInterface $this->reason = $reason; } + /** + * @template TFulfilledValue = never + * @template TFulfilledReason = never + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param (callable(TValue): (TFulfilledValue|PromiseInterface))|null $onFulfilled Invoked when the promise fulfills. + * @param (callable(TReason): (TRejectedValue|PromiseInterface))|null $onRejected Invoked when the promise is rejected. + * + * @return ($onRejected is null ? self : PromiseInterface) + */ public function then( - callable $onFulfilled = null, - callable $onRejected = null - ) { + ?callable $onFulfilled = null, + ?callable $onRejected = null + ): PromiseInterface { // If there's no onRejected callback then just return self. if (!$onRejected) { return $this; @@ -35,17 +59,14 @@ class RejectedPromise implements PromiseInterface $queue = Utils::queue(); $reason = $this->reason; $p = new Promise([$queue, 'run']); - $queue->add(static function () use ($p, $reason, $onRejected) { - if (Is::pending($p)) { + $queue->add(static function () use ($p, $reason, $onRejected): void { + if (State::isPending($p)) { try { // Return a resolved promise if onRejected does not throw. $p->resolve($onRejected($reason)); } catch (\Throwable $e) { // onRejected threw, so return a rejected promise. $p->reject($e); - } catch (\Exception $e) { - // onRejected threw, so return a rejected promise. - $p->reject($e); } } }); @@ -53,12 +74,20 @@ class RejectedPromise implements PromiseInterface return $p; } - public function otherwise(callable $onRejected) + /** + * @template TRejectedValue = never + * @template TRejectedReason = never + * + * @param callable(TReason): (TRejectedValue|PromiseInterface) $onRejected Invoked when the promise is rejected. + * + * @return PromiseInterface + */ + public function otherwise(callable $onRejected): PromiseInterface { return $this->then(null, $onRejected); } - public function wait($unwrap = true, $defaultDelivery = null) + public function wait(bool $unwrap = true) { if ($unwrap) { throw Create::exceptionFor($this->reason); @@ -67,24 +96,24 @@ class RejectedPromise implements PromiseInterface return null; } - public function getState() + public function getState(): string { return self::REJECTED; } - public function resolve($value) + public function resolve($value = null): void { - throw new \LogicException("Cannot resolve a rejected promise"); + throw new \LogicException('Cannot resolve a rejected promise'); } - public function reject($reason) + public function reject($reason): void { if ($reason !== $this->reason) { - throw new \LogicException("Cannot reject a rejected promise"); + throw new \LogicException('Cannot reject a rejected promise'); } } - public function cancel() + public function cancel(): void { // pass } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php index e2f137707..47dca8624 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/RejectionException.php @@ -1,5 +1,7 @@ reason = $reason; $message = 'The promise was rejected'; if ($description) { - $message .= ' with reason: ' . $description; + $message .= ' with reason: '.$description; } elseif (is_string($reason) || (is_object($reason) && method_exists($reason, '__toString')) ) { - $message .= ' with reason: ' . $this->reason; + $message .= ' with reason: '.$this->reason; } elseif ($reason instanceof \JsonSerializable) { - $message .= ' with reason: ' - . json_encode($this->reason, JSON_PRETTY_PRINT); + $message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT); } parent::__construct($message); diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/State.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/State.php new file mode 100644 index 000000000..35d98a781 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/State.php @@ -0,0 +1,44 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + */ + public static function isSettled(PromiseInterface $promise): bool + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + */ + public static function isFulfilled(PromiseInterface $promise): bool + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + */ + public static function isRejected(PromiseInterface $promise): bool + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php index f0fba2c59..e86c0cd7c 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -1,5 +1,7 @@ run(); + * + * @final */ class TaskQueue implements TaskQueueInterface { - private $enableShutdown = true; - private $queue = []; + use NonSerializableTrait; - public function __construct($withShutdown = true) + private bool $enableShutdown = true; + /** @var list */ + private array $queue = []; + + public function __construct(bool $withShutdown = true) { if ($withShutdown) { - register_shutdown_function(function () { + register_shutdown_function(function (): void { if ($this->enableShutdown) { // Only run the tasks if an E_ERROR didn't occur. $err = error_get_last(); @@ -31,20 +38,23 @@ class TaskQueue implements TaskQueueInterface } } - public function isEmpty() + public function isEmpty(): bool { return !$this->queue; } - public function add(callable $task) + /** + * @param callable(): void $task + */ + public function add(callable $task): void { $this->queue[] = $task; } - public function run() + public function run(): void { while ($task = array_shift($this->queue)) { - /** @var callable $task */ + /** @var callable(): void $task */ $task(); } } @@ -60,7 +70,7 @@ class TaskQueue implements TaskQueueInterface * * Note: This shutdown will occur before any destructors are triggered. */ - public function disableShutdown() + public function disableShutdown(): void { $this->enableShutdown = false; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php index 723d4d54e..d44b7f6fd 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php +++ b/data/web/inc/lib/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -1,24 +1,26 @@ * - * @param TaskQueueInterface $assign Optionally specify a new queue instance. - * - * @return TaskQueueInterface + * @param TaskQueueInterface|null $assign Optionally specify a new queue instance. */ - public static function queue(TaskQueueInterface $assign = null) + public static function queue(?TaskQueueInterface $assign = null): TaskQueueInterface { static $queue; @@ -35,26 +39,26 @@ final class Utils } /** - * Adds a function to run in the task queue when it is next `run()` and - * returns a promise that is fulfilled or rejected with the result. + * Adds a task to the global queue and returns a promise that is fulfilled + * or rejected with the task result. * - * @param callable $task Task function to run. + * @template TValue * - * @return PromiseInterface + * @param callable(): TValue $task Task function to run. + * + * @return PromiseInterface */ - public static function task(callable $task) + public static function task(callable $task): PromiseInterface { $queue = self::queue(); $promise = new Promise([$queue, 'run']); - $queue->add(function () use ($task, $promise) { + $queue->add(function () use ($task, $promise): void { try { - if (Is::pending($promise)) { + if (State::isPending($promise)) { $promise->resolve($task()); } } catch (\Throwable $e) { $promise->reject($e); - } catch (\Exception $e) { - $promise->reject($e); } }); @@ -62,7 +66,7 @@ final class Utils } /** - * Synchronously waits on a promise to resolve and returns an inspection + * Synchronously waits on a promise to settle and returns an inspection * state array. * * Returns a state associative array containing a "state" key mapping to a @@ -71,43 +75,78 @@ final class Utils * promise. If the promise is rejected, the array will contain a "reason" * key mapping to the rejection reason of the promise. * - * @param PromiseInterface $promise Promise or value. + * @template TValue + * @template TReason * - * @return array + * @param PromiseInterface $promise Promise to inspect. + * + * @return array{state: PromiseInterface::FULFILLED, value: TValue}|array{state: PromiseInterface::REJECTED, reason: TReason|\Throwable}|array{state: PromiseInterface::PENDING} */ - public static function inspect(PromiseInterface $promise) + public static function inspect(PromiseInterface $promise): array { + $result = null; + $getResult = static function () use (&$result): ?array { + return $result; + }; + + $inspection = $promise->then( + static function ($value) use (&$result): void { + $result = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + static function ($reason) use (&$result): void { + $result = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + ); + try { - return [ - 'state' => PromiseInterface::FULFILLED, - 'value' => $promise->wait() - ]; - } catch (RejectionException $e) { - return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + $inspection->wait(false); } catch (\Throwable $e) { - return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; - } catch (\Exception $e) { + $settled = $getResult(); + if (null !== $settled) { + return $settled; + } + + if (State::isSettled($promise)) { + try { + self::queue()->run(); + } catch (\Throwable $queueError) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $queueError]; + } + + $settled = $getResult(); + if (null !== $settled) { + return $settled; + } + } + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; } + + return $getResult() ?? ['state' => $promise->getState()]; } /** * Waits on all of the provided promises, but does not unwrap rejected - * promises as thrown exception. + * promises as a thrown exception. * - * Returns an array of inspection state arrays. + * Returns an array of inspection state arrays keyed like the input + * iterable. * * @see inspect for the inspection state array format. * - * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * @template TKey of array-key + * @template TValue + * @template TReason * - * @return array + * @param iterable> $promises Traversable of promises to wait upon. + * + * @return array */ - public static function inspectAll($promises) + public static function inspectAll(iterable $promises): array { $results = []; foreach ($promises as $key => $promise) { - $results[$key] = inspect($promise); + $results[$key] = self::inspect($promise); } return $results; @@ -120,14 +159,17 @@ final class Utils * order the promises were provided). An exception is thrown if any of the * promises are rejected. * - * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * @template TKey of array-key + * @template TValue + * @template TReason * - * @return array + * @param iterable> $promises Iterable of PromiseInterface objects to wait on. * - * @throws \Exception on error - * @throws \Throwable on error in PHP >=7 + * @return array + * + * @throws \Throwable on error */ - public static function unwrap($promises) + public static function unwrap(iterable $promises): array { $results = []; foreach ($promises as $key => $promise) { @@ -145,34 +187,45 @@ final class Utils * respective positions to the original array. If any promise in the array * rejects, the returned promise is rejected with the rejection reason. * - * @param mixed $promises Promises or values. - * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * The config array accepts a concurrency option for lazy iterables. Other + * config keys are ignored by this wrapper. * - * @return PromiseInterface + * @template TKey of array-key + * @template TValue + * @template TReason + * + * @param iterable> $promises Promises or values. + * @param bool $recursive If true, resolves newly-added entries until no unprocessed entries or pending promises remain. + * @param array{concurrency?: int|(callable(int): int)} $config Configuration options. + * + * @return PromiseInterface, TReason|\Throwable> */ - public static function all($promises, $recursive = false) + public static function all(iterable $promises, bool $recursive = false, array $config = []): PromiseInterface { $results = []; $promise = Each::of( $promises, - function ($value, $idx) use (&$results) { + function ($value, $idx) use (&$results): void { $results[$idx] = $value; }, - function ($reason, $idx, Promise $aggregate) { - $aggregate->reject($reason); - } + function ($reason, $idx, PromiseInterface $aggregate): void { + if (State::isPending($aggregate)) { + $aggregate->reject($reason); + } + }, + $config )->then(function () use (&$results) { ksort($results); + return $results; }); if (true === $recursive) { - $promise = $promise->then(function ($results) use ($recursive, &$promises) { - foreach ($promises as $promise) { - if (Is::pending($promise)) { - return self::all($promises, $recursive); - } + $promise = $promise->then(function ($results) use (&$promises, $config) { + if (self::shouldRecurse($promises, $results)) { + return self::all($promises, true, $config); } + return $results; }); } @@ -186,25 +239,28 @@ final class Utils * * When count amount of promises have been fulfilled, the returned promise * is fulfilled with an array that contains the fulfillment values of the - * winners in order of resolution. + * winners, in the order they appear in the input. * * This promise is rejected with a {@see AggregateException} if the number * of fulfilled promises is less than the desired $count. * - * @param int $count Total number of promises. - * @param mixed $promises Promises or values. + * @template TValue + * @template TReason * - * @return PromiseInterface + * @param int $count Total number of promises. + * @param iterable> $promises Promises or values. + * + * @return PromiseInterface, \Throwable> */ - public static function some($count, $promises) + public static function some(int $count, iterable $promises): PromiseInterface { $results = []; $rejections = []; - return Each::of( + $promise = Each::of( $promises, - function ($value, $idx, PromiseInterface $p) use (&$results, $count) { - if (Is::settled($p)) { + function ($value, $idx, PromiseInterface $p) use (&$results, $count): void { + if (State::isSettled($p)) { return; } $results[$idx] = $value; @@ -212,7 +268,7 @@ final class Utils $p->resolve(null); } }, - function ($reason) use (&$rejections) { + function ($reason) use (&$rejections): void { $rejections[] = $reason; } )->then( @@ -224,53 +280,111 @@ final class Utils ); } ksort($results); + return array_values($results); } ); + + /** @var PromiseInterface, \Throwable> $promise */ + return $promise; } /** * Like some(), with 1 as count. However, if the promise fulfills, the * fulfillment value is not an array of 1 but the value directly. * - * @param mixed $promises Promises or values. + * @template TValue + * @template TReason * - * @return PromiseInterface + * @param iterable> $promises Promises or values. + * + * @return PromiseInterface */ - public static function any($promises) + public static function any(iterable $promises): PromiseInterface { - return self::some(1, $promises)->then(function ($values) { + return self::some(1, $promises)->then(function (array $values) { return $values[0]; }); } /** - * Returns a promise that is fulfilled when all of the provided promises have - * been fulfilled or rejected. + * Returns a promise that is fulfilled when all of the provided promises + * have been fulfilled or rejected. * - * The returned promise is fulfilled with an array of inspection state arrays. + * The returned promise is fulfilled with an array of inspection state + * arrays. + * + * The config array accepts a concurrency option for lazy iterables. Other + * config keys are ignored by this wrapper. * * @see inspect for the inspection state array format. * - * @param mixed $promises Promises or values. + * @template TKey of array-key + * @template TValue + * @template TReason * - * @return PromiseInterface + * @param iterable> $promises Promises or values. + * @param bool $recursive If true, settles newly-added entries until no unprocessed entries or pending promises remain. + * @param array{concurrency?: int|(callable(int): int)} $config Configuration options. + * + * @return PromiseInterface, \Throwable> */ - public static function settle($promises) + public static function settle(iterable $promises, bool $recursive = false, array $config = []): PromiseInterface { $results = []; - return Each::of( + $promise = Each::of( $promises, - function ($value, $idx) use (&$results) { + function ($value, $idx) use (&$results): void { $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; }, - function ($reason, $idx) use (&$results) { + function ($reason, $idx) use (&$results): void { $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; - } + }, + $config )->then(function () use (&$results) { ksort($results); + return $results; }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use (&$promises, $config) { + if (self::shouldRecurse($promises, $results)) { + return self::settle($promises, true, $config); + } + + return $results; + }); + } + + return $promise; + } + + /** + * @template TKey of array-key + * + * @param iterable $promises Promises or values. + * @param array $results Results already collected for a pass. + */ + private static function shouldRecurse(iterable $promises, array $results): bool + { + // A consumed generator cannot be traversed again, so a recursive + // pass has nothing further to observe. + if ($promises instanceof \Generator) { + return false; + } + + foreach ($promises as $key => $promise) { + if (!array_key_exists($key, $results)) { + return true; + } + + if ($promise instanceof PromiseInterface && State::isPending($promise)) { + return true; + } + } + + return false; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions.php deleted file mode 100644 index c03d39d02..000000000 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions.php +++ /dev/null @@ -1,363 +0,0 @@ - - * while ($eventLoop->isRunning()) { - * GuzzleHttp\Promise\queue()->run(); - * } - * - * - * @param TaskQueueInterface $assign Optionally specify a new queue instance. - * - * @return TaskQueueInterface - * - * @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead. - */ -function queue(TaskQueueInterface $assign = null) -{ - return Utils::queue($assign); -} - -/** - * Adds a function to run in the task queue when it is next `run()` and returns - * a promise that is fulfilled or rejected with the result. - * - * @param callable $task Task function to run. - * - * @return PromiseInterface - * - * @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead. - */ -function task(callable $task) -{ - return Utils::task($task); -} - -/** - * Creates a promise for a value if the value is not a promise. - * - * @param mixed $value Promise or value. - * - * @return PromiseInterface - * - * @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. - */ -function promise_for($value) -{ - return Create::promiseFor($value); -} - -/** - * Creates a rejected promise for a reason if the reason is not a promise. If - * the provided reason is a promise, then it is returned as-is. - * - * @param mixed $reason Promise or reason. - * - * @return PromiseInterface - * - * @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. - */ -function rejection_for($reason) -{ - return Create::rejectionFor($reason); -} - -/** - * Create an exception for a rejected promise value. - * - * @param mixed $reason - * - * @return \Exception|\Throwable - * - * @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead. - */ -function exception_for($reason) -{ - return Create::exceptionFor($reason); -} - -/** - * Returns an iterator for the given value. - * - * @param mixed $value - * - * @return \Iterator - * - * @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead. - */ -function iter_for($value) -{ - return Create::iterFor($value); -} - -/** - * Synchronously waits on a promise to resolve and returns an inspection state - * array. - * - * Returns a state associative array containing a "state" key mapping to a - * valid promise state. If the state of the promise is "fulfilled", the array - * will contain a "value" key mapping to the fulfilled value of the promise. If - * the promise is rejected, the array will contain a "reason" key mapping to - * the rejection reason of the promise. - * - * @param PromiseInterface $promise Promise or value. - * - * @return array - * - * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead. - */ -function inspect(PromiseInterface $promise) -{ - return Utils::inspect($promise); -} - -/** - * Waits on all of the provided promises, but does not unwrap rejected promises - * as thrown exception. - * - * Returns an array of inspection state arrays. - * - * @see inspect for the inspection state array format. - * - * @param PromiseInterface[] $promises Traversable of promises to wait upon. - * - * @return array - * - * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead. - */ -function inspect_all($promises) -{ - return Utils::inspectAll($promises); -} - -/** - * Waits on all of the provided promises and returns the fulfilled values. - * - * Returns an array that contains the value of each promise (in the same order - * the promises were provided). An exception is thrown if any of the promises - * are rejected. - * - * @param iterable $promises Iterable of PromiseInterface objects to wait on. - * - * @return array - * - * @throws \Exception on error - * @throws \Throwable on error in PHP >=7 - * - * @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead. - */ -function unwrap($promises) -{ - return Utils::unwrap($promises); -} - -/** - * Given an array of promises, return a promise that is fulfilled when all the - * items in the array are fulfilled. - * - * The promise's fulfillment value is an array with fulfillment values at - * respective positions to the original array. If any promise in the array - * rejects, the returned promise is rejected with the rejection reason. - * - * @param mixed $promises Promises or values. - * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. - * - * @return PromiseInterface - * - * @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead. - */ -function all($promises, $recursive = false) -{ - return Utils::all($promises, $recursive); -} - -/** - * Initiate a competitive race between multiple promises or values (values will - * become immediately fulfilled promises). - * - * When count amount of promises have been fulfilled, the returned promise is - * fulfilled with an array that contains the fulfillment values of the winners - * in order of resolution. - * - * This promise is rejected with a {@see AggregateException} if the number of - * fulfilled promises is less than the desired $count. - * - * @param int $count Total number of promises. - * @param mixed $promises Promises or values. - * - * @return PromiseInterface - * - * @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead. - */ -function some($count, $promises) -{ - return Utils::some($count, $promises); -} - -/** - * Like some(), with 1 as count. However, if the promise fulfills, the - * fulfillment value is not an array of 1 but the value directly. - * - * @param mixed $promises Promises or values. - * - * @return PromiseInterface - * - * @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead. - */ -function any($promises) -{ - return Utils::any($promises); -} - -/** - * Returns a promise that is fulfilled when all of the provided promises have - * been fulfilled or rejected. - * - * The returned promise is fulfilled with an array of inspection state arrays. - * - * @see inspect for the inspection state array format. - * - * @param mixed $promises Promises or values. - * - * @return PromiseInterface - * - * @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead. - */ -function settle($promises) -{ - return Utils::settle($promises); -} - -/** - * Given an iterator that yields promises or values, returns a promise that is - * fulfilled with a null value when the iterator has been consumed or the - * aggregate promise has been fulfilled or rejected. - * - * $onFulfilled is a function that accepts the fulfilled value, iterator index, - * and the aggregate promise. The callback can invoke any necessary side - * effects and choose to resolve or reject the aggregate if needed. - * - * $onRejected is a function that accepts the rejection reason, iterator index, - * and the aggregate promise. The callback can invoke any necessary side - * effects and choose to resolve or reject the aggregate if needed. - * - * @param mixed $iterable Iterator or array to iterate over. - * @param callable $onFulfilled - * @param callable $onRejected - * - * @return PromiseInterface - * - * @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead. - */ -function each( - $iterable, - callable $onFulfilled = null, - callable $onRejected = null -) { - return Each::of($iterable, $onFulfilled, $onRejected); -} - -/** - * Like each, but only allows a certain number of outstanding promises at any - * given time. - * - * $concurrency may be an integer or a function that accepts the number of - * pending promises and returns a numeric concurrency limit value to allow for - * dynamic a concurrency size. - * - * @param mixed $iterable - * @param int|callable $concurrency - * @param callable $onFulfilled - * @param callable $onRejected - * - * @return PromiseInterface - * - * @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead. - */ -function each_limit( - $iterable, - $concurrency, - callable $onFulfilled = null, - callable $onRejected = null -) { - return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected); -} - -/** - * Like each_limit, but ensures that no promise in the given $iterable argument - * is rejected. If any promise is rejected, then the aggregate promise is - * rejected with the encountered rejection. - * - * @param mixed $iterable - * @param int|callable $concurrency - * @param callable $onFulfilled - * - * @return PromiseInterface - * - * @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead. - */ -function each_limit_all( - $iterable, - $concurrency, - callable $onFulfilled = null -) { - return Each::ofLimitAll($iterable, $concurrency, $onFulfilled); -} - -/** - * Returns true if a promise is fulfilled. - * - * @return bool - * - * @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead. - */ -function is_fulfilled(PromiseInterface $promise) -{ - return Is::fulfilled($promise); -} - -/** - * Returns true if a promise is rejected. - * - * @return bool - * - * @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead. - */ -function is_rejected(PromiseInterface $promise) -{ - return Is::rejected($promise); -} - -/** - * Returns true if a promise is fulfilled or rejected. - * - * @return bool - * - * @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead. - */ -function is_settled(PromiseInterface $promise) -{ - return Is::settled($promise); -} - -/** - * Create a new coroutine. - * - * @see Coroutine - * - * @return PromiseInterface - * - * @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead. - */ -function coroutine(callable $generatorFn) -{ - return Coroutine::of($generatorFn); -} diff --git a/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions_include.php b/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions_include.php deleted file mode 100644 index 34cd1710a..000000000 --- a/data/web/inc/lib/vendor/guzzlehttp/promises/src/functions_include.php +++ /dev/null @@ -1,6 +0,0 @@ - 0 ? 'INF' : '-INF')` otherwise. +- Never call `strtolower()`, `strtoupper()`, `strcasecmp()`, `stripos()`, or + other locale-sensitive case functions; use the locale-independent + `Utils::asciiToLower()`, `Utils::asciiToUpper()`, `Utils::caselessEquals()`, + and `Utils::caselessContains()` helpers instead. +- Changes in behavior need a `CHANGELOG.md` entry in the unreleased section of + the target branch and an `UPGRADING.md` note when the behavior differs between + major versions. + +## Documentation + +- Wrap markdown prose and PHPDoc text to 80 columns using greedy wrapping. Never + split a markdown link or an inline code span across a line break; a line that + cannot be broken may exceed the limit. Avoid em dashes. +- Keep PHPDoc and the corresponding `docs/` pages in sync: shared prose is + deliberately word-for-word identical, including boilerplate copied verbatim + between related functions, so apply the same edit to every copy. Only + formatting and linking may differ, such as a docs link becoming a PHPDoc + `@see` tag; the wording must never drift. diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/CHANGELOG.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/CHANGELOG.md index 49e0c12a4..28541826b 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/CHANGELOG.md +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/CHANGELOG.md @@ -5,7 +5,375 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 3.2.0 - 2026-10-18 + +### Fixed + +- Fix `InflateStream` ignoring source read failures that follow decoded bytes +- Fix `AppendStream` discarding bytes read before a timeout + +## 3.1.1 - 2026-10-18 + +### Fixed + +- Fix `StreamWrapper` flushes reporting failure +- Fix `CachingStream` skipping unread bytes after repeated writes + +## 3.1.0 - 2026-08-24 + +### Added + +- Add `Utils::redactUriForMessage()` and `Utils::redactUriStringForMessage()` for URI diagnostics +- Add support for PHP 8.6 + +### Changed + +- Omit rejected header values and sensitive URI components from automatic exception messages + +## 3.0.1 - 2026-08-24 + +### Fixed + +- Prefix relative paths that begin with a colon segment with `./` instead of throwing +- Apply the `/.` prefix for authority-less `//` paths to percent-encoding normalizations as well +- Keep colon-leading first path segments when reading the paths of scheme-less non-native URIs +- Stop throwing when removing the default `file` host strands a `//` path, prefixing it with `/.` + +## 3.0.0 - 2026-07-20 + +### Added + +- Add `DiagnosticValue::escape()` to escape controls and malformed UTF-8 in diagnostics +- Add `GuzzleHttp\Psr7\Exception\TimeoutException` for timed-out stream operations +- Add `GuzzleHttp\Psr7\Utils::redactUserInfoInString()` to redact the userinfo of a raw URI string within text +- Promote `GuzzleHttp\Psr7\Rfc3986` to public API with `isValid*()` predicates and `canonicalizeIpv6()` +- Add `GuzzleHttp\Psr7\UriNormalizer::CANONICALIZE_IPV6_HOST` to `PRESERVING_NORMALIZATIONS` + +### Changed + +- Require `psr/http-message:^2.0` and add native parameter and return types +- Require `psr/http-factory:^1.1` +- Reject native PHP serialization of stream implementations +- Preserve request method casing, except `ServerRequest::fromGlobals()` still uppercases +- Reject empty arrays and non-string values as header values +- Reject invalid uploaded file trees and invalid parsed body values +- Reject uploaded file specs missing `tmp_name`, `size`, or `error` +- Reject non-integer and negative uploaded file `error` values +- Reject invalid stream/upload sizes, buffer high-water marks, and dropping-stream limits +- Rewind seekable uploaded-file streams before copying in `UploadedFile::moveTo()` +- Reject negative `read()` lengths across all stream implementations +- Detect the `+` flag anywhere in a mode for `Stream::isReadable()`/`isWritable()` +- Reject empty strings returned by `PumpStream` source callables +- Discard buffered bytes on `PumpStream` close and detach +- Restore the original stream position after `Message::bodySummary()` +- Allow `null` for the `Message::bodySummary()` truncation length to use the default +- Validate `LimitStream` offset/limit and track non-seekable offset by bytes skipped +- Make `FnStream` close and detach terminal, calling close callbacks at most once +- Suppress exceptions from `FnStream` close callbacks during destructor cleanup +- Make `CachingStream::close()` idempotent, preserving remote cleanup after detach +- Do not move the `CachingStream` cursor when a `SEEK_END` target on an unknown-size stream is rejected +- Normalize multiple leading slashes in `Uri::getPath()` and origin-form request targets +- Serialize authority-less `file` URIs with rootless paths without the `//` separator +- Serialize authority-less `file` URIs with empty paths as `file:` instead of the unparseable `file://` +- Remove dot segments above the root per RFC 3986, prefixing authority-less `//` paths with `/.` +- Return a network-path reference from `UriResolver::relativize()` when an empty-path target requires one +- Stop returning an empty reference from `UriResolver::relativize()` when it would inherit the base fragment +- Stop throwing from `UriResolver::relativize()` when an equal-path target's last path segment contains a colon +- Harden URI host validation (delimiters, backslashes, IPv6, embedded ports); require schemes to start with a letter +- Validate `Uri::fromParts()` ports instead of casting them +- Treat ports 80 and 443 as defaults for the `ws` and `wss` schemes +- Use the `ws` and `wss` default ports in `UriComparator::isCrossOrigin()` port comparisons +- Redact all non-empty URI userinfo in `Utils::redactUserInfo()` +- Rebuild server request URIs from `$_SERVER` by `REQUEST_METHOD`, using target authority before `SERVER_PORT` +- Remove userinfo from absolute-form `REQUEST_URI` targets in `ServerRequest::fromGlobals()` +- Reject zero-port `HTTP_HOST` and malformed `SERVER_PORT` in `ServerRequest::getUriFromGlobals()` +- Reject malformed `REQUEST_METHOD` and `SERVER_PROTOCOL` server values in `ServerRequest::fromGlobals()` +- Reject zero-port `Host` and normalize leading-zero ports in `Message::parseRequest()` +- Reject duplicate `Host` headers and validate present values for all request-target forms +- Reject zero-port, hostless, and userinfo absolute-form request targets in `Message::parseRequest()` +- Synchronize the `Host` header in `Request::withUri()` when the URI changes or Host is empty +- Include the URI port in `Host` headers synthesized by `Message::toString()` +- Validate the Host header synthesized by `Message::toString()` from the request URI +- Include non-default URI ports in `Host` headers set by `Utils::modifyRequest()` URI changes +- Accept `OPTIONS *` and `CONNECT` authority-form request targets in `Message::parseRequest()` +- Hide credential-bearing URI, server, and cookie arguments in stack traces on PHP 8.2+ +- Reject malformed HTTP request/response start-lines +- Unfold obsolete HTTP/1.0 line folding for all valid request method tokens +- Reject empty and control-character request targets in `Request::withRequestTarget()` +- Validate iterator chunks passed to `Utils::streamFor()` +- Validate unsupported values passed to `Query::build()` +- Reject non-finite float values in `Query::build()` and `MultipartStream` contents +- Reject non-finite float values in iterator chunks passed to `Utils::streamFor()` +- `Utils::streamFor()` now rejects non-string scalar bodies +- `Uri::withQueryValues()` now rejects non-string values +- Reject invalid `Utils::modifyRequest()` change values +- Use PHP debug type names in type error messages +- Changed `Utils::copyToStream()` to throw when destination streams cannot make progress +- Throw `TimeoutException` from `Stream` read/write and `Utils` copy/hash/readLine on stream timeouts +- Throw `TimeoutException` from `AppendStream::read()`, `CachingStream::read()`, and `Utils::tryGetContents()` on stream timeouts +- Re-throw `TimeoutException` from `InflateStream` when the decoded source stream times out +- Close the compressed source stream from `InflateStream::close()` +- Return the number of bytes copied from `Utils::copyToStream()` +- Throw `OverflowException` when stream byte counts or offsets exceed `PHP_INT_MAX` +- Translate `StreamWrapper` runtime failures to PHP stream failure values +- Stop adding default `Content-Length` to `multipart/form-data` parts (RFC 7578 §4.8) +- Escape multipart `Content-Disposition` parameters and reject unsafe boundaries and part headers +- Preserve trailing whitespace in custom `MultipartStream` part header values +- Preserve explicit custom `MultipartStream` boundary `'0'` instead of replacing it with a generated boundary +- Made static utility classes non-instantiable +- Validate bracketed IP-literal hosts consistently between parsing and `withHost()`, including userinfo forms +- Percent-encode raw control bytes in userinfo before bracketed IP-literal hosts instead of parsing mutated values +- Reject invalid UTF-8 and preserve percent-sequences in userinfo before bracketed IP-literal hosts +- Normalize percent-encoded octets in the URI host to uppercase hex +- Canonicalize IPv6 hosts to RFC 5952 form in `Uri` construction, `fromParts()`, and `withHost()` +- Canonicalize bracketed IPv6 hosts in `UriComparator::isCrossOrigin()` +- Reject malformed percent-sequences and percent-encoded bytes forbidden by the URI host policy +- Extend `CAPITALIZE_PERCENT_ENCODING` and `DECODE_UNRESERVED_CHARACTERS` to userinfo and host +- Trim header list elements with only spaces, horizontal tabs, and line terminators in `Header::splitList()` +- Report header parameter PCRE failures explicitly in `Header::parse()` +- Escape controls and malformed UTF-8 consistently in generated exception messages + +### Removed + +- Dropped support for PHP 7.2 and 7.3 +- Removed the `ralouphie/getallheaders` dependency +- Removed deprecated `Header::normalize()` method + +## 2.13.0 - 2026-07-16 + +### Added + +- Add `Utils::` `asciiToLower`, `asciiToUpper`, `asciiUcFirst`, `caselessEquals`, `caselessContains` + +### Changed + +- Use locale-independent ASCII case folding everywhere case is normalized +- Trigger a runtime deprecation for previously deprecated functionality in 2.3.0 + +## 2.12.5 - 2026-07-13 + +### Fixed + +- Compare header names and hosts with locale-independent ASCII lowercasing +- Compare hosts without locale sensitivity when detecting cross-origin redirects + +## 2.12.4 - 2026-07-08 + +### Changed + +- Pass explicit trim characters ahead of the PHP 8.6 trim default change + +### Fixed + +- Anchor server port and response start-line patterns to the true end of input +- Treat host-less origin-form request targets starting with `//` as paths in `Message::parseRequest()` +- Reject raw DEL bytes in bracketed IP-literal hosts instead of parsing a mutated host +- Reject invalid bytes after a bracketed IP-literal host instead of reparsing a different host + +## 2.12.3 - 2026-06-23 + +### Security + +- Validate the URI host so `getHost()` matches the URI authority (GHSA-c2w2-prh8-qm98) + +## 2.12.2 - 2026-06-23 + +### Fixed + +- Report URI parsing, filtering, and normalization PCRE failures explicitly +- Report HTTP message parser PCRE failures explicitly +- Fail closed when PCRE validation fails for request targets and hosts + +## 2.12.1 - 2026-06-18 + +### Security + +- Reject CR/LF in HTTP method, protocol version, and reason phrase (GHSA-vm85-hxw5-5432) + +## 2.12.0 - 2026-06-16 + +### Deprecated + +- Deprecated non-finite float values in `Query::build()` that guzzlehttp/psr7 3.0 rejects +- Deprecated non-finite float multipart contents that guzzlehttp/psr7 3.0 rejects +- Deprecated non-string scalar bodies in `Utils::streamFor()`; cast them to a string for 3.0 +- Deprecated non-string `Uri::withQueryValues()` values; cast them to a string for 3.0 + +## 2.11.1 - 2026-06-12 + +### Fixed + +- Fixed non-finite float values emitting coercion warnings on PHP 8.5 + +## 2.11.0 - 2026-06-02 + +### Changed + +- Changed `Utils::modifyRequest()` to reject conflicting URI and `Host` header changes in the same call +- Changed `Header::parse()` to split semicolon-separated parameters without repeated regular expression lookaheads +- Changed `UriComparator::isCrossOrigin()` so only HTTP and HTTPS missing ports receive implicit default ports + +### Deprecated + +- Deprecated invalid PSR-7 arguments that guzzlehttp/psr7 3.0 will require native types for +- Deprecated non-string header values that guzzlehttp/psr7 3.0 will reject +- Deprecated empty header value arrays that guzzlehttp/psr7 3.0 will reject +- Deprecated URI schemes that do not match guzzlehttp/psr7 3.0 syntax requirements +- Deprecated multipart boundary and custom part header metadata that guzzlehttp/psr7 3.0 will reject +- Deprecated reliance on automatic uppercasing of request methods; guzzlehttp/psr7 3.0 preserves method casing +- Deprecated invalid `Utils::modifyRequest()` change values that guzzlehttp/psr7 3.0 will reject + +### Fixed + +- Fixed `Utils::copyToStream()` to retry short destination writes instead of dropping the unwritten remainder +- Fixed `Header::parse()` splitting of semicolon-separated parameters with escaped quotes + +## 2.10.4 - 2026-05-29 + +### Fixed + +- Apply `UriNormalizer` percent-encoding normalizations to URI fragments +- Make `LimitStream::getSize()` return `0` for slices past the underlying stream end +- Make `AppendStream::read()` return an empty string when no streams are attached +- Make `CachingStream::read()` throw on an incomplete cache-target write instead of silently corrupting replays +- Prevent `CachingStream::seek()` from looping indefinitely when the remote stream makes no progress + +## 2.10.3 - 2026-05-27 + +### Fixed + +- Fixed URI parsing for IPv6 literals containing embedded IPv4 addresses +- Fixed malformed UTF-8 URI strings being parsed as empty URIs + +## 2.10.2 - 2026-05-25 + +### Security + +- Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw) +- Reject malformed Host values when constructing request URIs (GHSA-34xg-wgjx-8xph) + +### Fixed + +- Make `ServerRequest::fromGlobals()` robust against unexpected HTTP header value types in `$_SERVER` + +## 2.10.1 - 2026-05-20 + +### Fixed + +- Fix `Utils::modifyRequest()` with numeric header names + +## 2.10.0 - 2026-05-19 + +### Changed + +- Harden `ServerRequest::fromGlobals()` against malformed `$_SERVER` values +- Prevent custom stream metadata from affecting internal size handling +- Throw when `StreamWrapper::getResource()` cannot create a resource +- Preserve custom request implementations in `Utils::modifyRequest()` +- Preserve custom URI implementations in `UriResolver::resolve()` +- Make `Uri::__toString()` side-effect-free + +## 2.9.1 - 2026-05-19 + +### Fixed + +- Fix parsing of relative path references containing a colon in a non-initial path segment +- Fix `CachingStream::detach()` returning an incomplete resource before the decorated stream has been fully read +- Fix `Message::bodySummary()` returning `null` when truncating printable UTF-8 bodies inside a multibyte character + +## 2.9.0 - 2026-03-10 + +### Added + +- Added nested array expansion support to `MultipartStream` +- Added `@return static` to `MessageTrait` methods + +### Changed + +- Updated MIME type mappings + +## 2.8.1 - 2026-03-10 + +### Fixed + +- Encode `+` signs in `Uri::withQueryValue()` and `Uri::withQueryValues()` to prevent them being interpreted as spaces + +## 2.8.0 - 2025-08-23 + +### Added + +- Allow empty lists as header values + +### Changed + +- PHP 8.5 support + +## 2.7.1 - 2025-03-27 + +### Fixed + +- Fixed uppercase IPv6 addresses in URI + +### Changed + +- Improve uploaded file error message + +## 2.7.0 - 2024-07-18 + +### Added + +- Add `Utils::redactUserInfo()` method +- Add ability to encode bools as ints in `Query::build` + +## 2.6.3 - 2024-07-18 + +### Fixed + +- Make `StreamWrapper::stream_stat()` return `false` if inner stream's size is `null` + +### Changed + +- PHP 8.4 support + +## 2.6.2 - 2023-12-03 + +### Fixed + +- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints + +### Changed + +- Updated links in docs to their canonical versions +- Replaced `call_user_func*` with native calls + +## 2.6.1 - 2023-08-27 + +### Fixed + +- Properly handle the fact that PHP transforms numeric strings in array keys to ints + +## 2.6.0 - 2023-08-03 + +### Changed + +- Updated the mime type map to add some new entries, fix a couple of invalid entries, and remove an invalid entry +- Fallback to `application/octet-stream` if we are unable to guess the content type for a multipart file upload + +## 2.5.1 - 2023-08-03 + +### Fixed + +- Corrected mime type for `.acc` files to `audio/aac` + +### Changed + +- PHP 8.3 support + +## 2.5.0 - 2023-04-17 + +### Changed + +- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0` ## 2.4.5 - 2023-04-17 diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/CLAUDE.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md index 9566a7d47..5e6bae175 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/README.md @@ -1,880 +1,87 @@ -# PSR-7 Message Implementation +# Guzzle PSR-7 -This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/) -message implementation, several stream decorators, and some helpful -functionality like query string parsing. +`guzzlehttp/psr7` is a PSR-7 HTTP message implementation for PHP. It provides +request, response, URI, uploaded file, and stream objects that work with Guzzle +and any other library using the PSR-7 interfaces. -![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) -![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) +Use this package directly when you need to create or inspect PSR-7 messages +without sending HTTP requests. If you only want to make HTTP requests, install +[`guzzlehttp/guzzle`](https://github.com/guzzle/guzzle/blob/8.0/README.md) +instead; it already depends on this package. +## Installation -# Installation - -```shell +```bash composer require guzzlehttp/psr7 ``` -# Stream implementation - -This package comes with a number of stream implementations and stream -decorators. - - -## AppendStream - -`GuzzleHttp\Psr7\AppendStream` - -Reads from multiple streams, one after the other. - -```php -use GuzzleHttp\Psr7; - -$a = Psr7\Utils::streamFor('abc, '); -$b = Psr7\Utils::streamFor('123.'); -$composed = new Psr7\AppendStream([$a, $b]); - -$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); - -echo $composed; // abc, 123. Above all listen to me. -``` - - -## BufferStream - -`GuzzleHttp\Psr7\BufferStream` - -Provides a buffer stream that can be written to fill a buffer, and read -from to remove bytes from the buffer. - -This stream returns a "hwm" metadata value that tells upstream consumers -what the configured high water mark of the stream is, or the maximum -preferred size of the buffer. - -```php -use GuzzleHttp\Psr7; - -// When more than 1024 bytes are in the buffer, it will begin returning -// false to writes. This is an indication that writers should slow down. -$buffer = new Psr7\BufferStream(1024); -``` - - -## CachingStream - -The CachingStream is used to allow seeking over previously read bytes on -non-seekable streams. This can be useful when transferring a non-seekable -entity body fails due to needing to rewind the stream (for example, resulting -from a redirect). Data that is read from the remote stream will be buffered in -a PHP temp stream so that previously read bytes are cached first in memory, -then on disk. - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); -$stream = new Psr7\CachingStream($original); - -$stream->read(1024); -echo $stream->tell(); -// 1024 - -$stream->seek(0); -echo $stream->tell(); -// 0 -``` - - -## DroppingStream - -`GuzzleHttp\Psr7\DroppingStream` - -Stream decorator that begins dropping data once the size of the underlying -stream becomes too full. - -```php -use GuzzleHttp\Psr7; - -// Create an empty stream -$stream = Psr7\Utils::streamFor(); - -// Start dropping data when the stream has more than 10 bytes -$dropping = new Psr7\DroppingStream($stream, 10); - -$dropping->write('01234567890123456789'); -echo $stream; // 0123456789 -``` - - -## FnStream - -`GuzzleHttp\Psr7\FnStream` - -Compose stream implementations based on a hash of functions. - -Allows for easy testing and extension of a provided stream without needing -to create a concrete class for a simple extension point. - -```php - -use GuzzleHttp\Psr7; - -$stream = Psr7\Utils::streamFor('hi'); -$fnStream = Psr7\FnStream::decorate($stream, [ - 'rewind' => function () use ($stream) { - echo 'About to rewind - '; - $stream->rewind(); - echo 'rewound!'; - } -]); - -$fnStream->rewind(); -// Outputs: About to rewind - rewound! -``` - - -## InflateStream - -`GuzzleHttp\Psr7\InflateStream` - -Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. - -This stream decorator converts the provided stream to a PHP stream resource, -then appends the zlib.inflate filter. The stream is then converted back -to a Guzzle stream resource to be used as a Guzzle stream. - - -## LazyOpenStream - -`GuzzleHttp\Psr7\LazyOpenStream` - -Lazily reads or writes to a file that is opened only after an IO operation -take place on the stream. - -```php -use GuzzleHttp\Psr7; - -$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); -// The file has not yet been opened... - -echo $stream->read(10); -// The file is opened and read from only when needed. -``` - - -## LimitStream - -`GuzzleHttp\Psr7\LimitStream` - -LimitStream can be used to read a subset or slice of an existing stream object. -This can be useful for breaking a large file into smaller pieces to be sent in -chunks (e.g. Amazon S3's multipart upload API). - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); -echo $original->getSize(); -// >>> 1048576 - -// Limit the size of the body to 1024 bytes and start reading from byte 2048 -$stream = new Psr7\LimitStream($original, 1024, 2048); -echo $stream->getSize(); -// >>> 1024 -echo $stream->tell(); -// >>> 0 -``` - - -## MultipartStream - -`GuzzleHttp\Psr7\MultipartStream` - -Stream that when read returns bytes for a streaming multipart or -multipart/form-data stream. - - -## NoSeekStream - -`GuzzleHttp\Psr7\NoSeekStream` - -NoSeekStream wraps a stream and does not allow seeking. - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor('foo'); -$noSeek = new Psr7\NoSeekStream($original); - -echo $noSeek->read(3); -// foo -var_export($noSeek->isSeekable()); -// false -$noSeek->seek(0); -var_export($noSeek->read(3)); -// NULL -``` - - -## PumpStream - -`GuzzleHttp\Psr7\PumpStream` - -Provides a read only stream that pumps data from a PHP callable. - -When invoking the provided callable, the PumpStream will pass the amount of -data requested to read to the callable. The callable can choose to ignore -this value and return fewer or more bytes than requested. Any extra data -returned by the provided callable is buffered internally until drained using -the read() function of the PumpStream. The provided callable MUST return -false when there is no more data to read. - - -## Implementing stream decorators - -Creating a stream decorator is very easy thanks to the -`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that -implement `Psr\Http\Message\StreamInterface` by proxying to an underlying -stream. Just `use` the `StreamDecoratorTrait` and implement your custom -methods. - -For example, let's say we wanted to call a specific function each time the last -byte is read from a stream. This could be implemented by overriding the -`read()` method. - -```php -use Psr\Http\Message\StreamInterface; -use GuzzleHttp\Psr7\StreamDecoratorTrait; - -class EofCallbackStream implements StreamInterface -{ - use StreamDecoratorTrait; - - private $callback; - - private $stream; - - public function __construct(StreamInterface $stream, callable $cb) - { - $this->stream = $stream; - $this->callback = $cb; - } - - public function read($length) - { - $result = $this->stream->read($length); - - // Invoke the callback when EOF is hit. - if ($this->eof()) { - call_user_func($this->callback); - } - - return $result; - } -} -``` - -This decorator could be added to any existing stream and used like so: - -```php -use GuzzleHttp\Psr7; - -$original = Psr7\Utils::streamFor('foo'); - -$eofStream = new EofCallbackStream($original, function () { - echo 'EOF!'; -}); - -$eofStream->read(2); -$eofStream->read(1); -// echoes "EOF!" -$eofStream->seek(0); -$eofStream->read(3); -// echoes "EOF!" -``` - - -## PHP StreamWrapper - -You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a -PSR-7 stream as a PHP stream resource. - -Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP -stream from a PSR-7 stream. - -```php -use GuzzleHttp\Psr7\StreamWrapper; - -$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); -$resource = StreamWrapper::getResource($stream); -echo fread($resource, 6); // outputs hello! -``` - - -# Static API - -There are various static methods available under the `GuzzleHttp\Psr7` namespace. - - -## `GuzzleHttp\Psr7\Message::toString` - -`public static function toString(MessageInterface $message): string` - -Returns the string representation of an HTTP message. - -```php -$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); -echo GuzzleHttp\Psr7\Message::toString($request); -``` - - -## `GuzzleHttp\Psr7\Message::bodySummary` - -`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` - -Get a short summary of the message body. - -Will return `null` if the response is not printable. - - -## `GuzzleHttp\Psr7\Message::rewindBody` - -`public static function rewindBody(MessageInterface $message): void` - -Attempts to rewind a message body and throws an exception on failure. - -The body of the message will only be rewound if a call to `tell()` -returns a value other than `0`. - - -## `GuzzleHttp\Psr7\Message::parseMessage` - -`public static function parseMessage(string $message): array` - -Parses an HTTP message into an associative array. - -The array contains the "start-line" key containing the start line of -the message, "headers" key containing an associative array of header -array values, and a "body" key containing the body of the message. - - -## `GuzzleHttp\Psr7\Message::parseRequestUri` - -`public static function parseRequestUri(string $path, array $headers): string` - -Constructs a URI for an HTTP request message. - - -## `GuzzleHttp\Psr7\Message::parseRequest` - -`public static function parseRequest(string $message): Request` - -Parses a request message string into a request object. - - -## `GuzzleHttp\Psr7\Message::parseResponse` - -`public static function parseResponse(string $message): Response` - -Parses a response message string into a response object. - - -## `GuzzleHttp\Psr7\Header::parse` - -`public static function parse(string|array $header): array` - -Parse an array of header values containing ";" separated data into an -array of associative arrays representing the header key value pair data -of the header. When a parameter does not contain a value, but just -contains a key, this function will inject a key with a '' string value. - - -## `GuzzleHttp\Psr7\Header::splitList` - -`public static function splitList(string|string[] $header): string[]` - -Splits a HTTP header defined to contain a comma-separated list into -each individual value: - -``` -$knownEtags = Header::splitList($request->getHeader('if-none-match')); -``` - -Example headers include `accept`, `cache-control` and `if-none-match`. - - -## `GuzzleHttp\Psr7\Header::normalize` (deprecated) - -`public static function normalize(string|array $header): array` - -`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist) -which performs the same operation with a cleaned up API and improved -documentation. - -Converts an array of header values that may contain comma separated -headers into an array of headers with no comma separated values. - - -## `GuzzleHttp\Psr7\Query::parse` - -`public static function parse(string $str, int|bool $urlEncoding = true): array` - -Parse a query string into an associative array. - -If multiple values are found for the same key, the value of that key -value pair will become an array. This function does not parse nested -PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` -will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. - - -## `GuzzleHttp\Psr7\Query::build` - -`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` - -Build a query string from an array of key value pairs. - -This function can use the return value of `parse()` to build a query -string. This function does not modify the provided keys when an array is -encountered (like `http_build_query()` would). - - -## `GuzzleHttp\Psr7\Utils::caselessRemove` - -`public static function caselessRemove(iterable $keys, $keys, array $data): array` - -Remove the items given by the keys, case insensitively from the data. - - -## `GuzzleHttp\Psr7\Utils::copyToStream` - -`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` - -Copy the contents of a stream into another stream until the given number -of bytes have been read. - - -## `GuzzleHttp\Psr7\Utils::copyToString` - -`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` - -Copy the contents of a stream into a string until the given number of -bytes have been read. - - -## `GuzzleHttp\Psr7\Utils::hash` - -`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` - -Calculate a hash of a stream. - -This method reads the entire stream to calculate a rolling hash, based on -PHP's `hash_init` functions. - - -## `GuzzleHttp\Psr7\Utils::modifyRequest` - -`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` - -Clone and modify a request with the given changes. - -This method is useful for reducing the number of clones needed to mutate -a message. - -- method: (string) Changes the HTTP method. -- set_headers: (array) Sets the given headers. -- remove_headers: (array) Remove the given headers. -- body: (mixed) Sets the given body. -- uri: (UriInterface) Set the URI. -- query: (string) Set the query string value of the URI. -- version: (string) Set the protocol version. - - -## `GuzzleHttp\Psr7\Utils::readLine` - -`public static function readLine(StreamInterface $stream, int $maxLength = null): string` - -Read a line from the stream up to the maximum allowed buffer length. - - -## `GuzzleHttp\Psr7\Utils::streamFor` - -`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` - -Create a new stream based on the input type. - -Options is an associative array that can contain the following keys: - -- metadata: Array of custom metadata. -- size: Size of the stream. - -This method accepts the following `$resource` types: - -- `Psr\Http\Message\StreamInterface`: Returns the value as-is. -- `string`: Creates a stream object that uses the given string as the contents. -- `resource`: Creates a stream object that wraps the given PHP stream resource. -- `Iterator`: If the provided value implements `Iterator`, then a read-only - stream object will be created that wraps the given iterable. Each time the - stream is read from, data from the iterator will fill a buffer and will be - continuously called until the buffer is equal to the requested read size. - Subsequent read calls will first read from the buffer and then call `next` - on the underlying iterator until it is exhausted. -- `object` with `__toString()`: If the object has the `__toString()` method, - the object will be cast to a string and then a stream will be returned that - uses the string value. -- `NULL`: When `null` is passed, an empty stream object is returned. -- `callable` When a callable is passed, a read-only stream object will be - created that invokes the given callable. The callable is invoked with the - number of suggested bytes to read. The callable can return any number of - bytes, but MUST return `false` when there is no more data to return. The - stream object that wraps the callable will invoke the callable until the - number of requested bytes are available. Any additional bytes will be - buffered and used in subsequent reads. - -```php -$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); -$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); - -$generator = function ($bytes) { - for ($i = 0; $i < $bytes; $i++) { - yield ' '; - } -} - -$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); -``` - - -## `GuzzleHttp\Psr7\Utils::tryFopen` - -`public static function tryFopen(string $filename, string $mode): resource` - -Safely opens a PHP stream resource using a filename. - -When fopen fails, PHP normally raises a warning. This function adds an -error handler that checks for errors and throws an exception instead. - - -## `GuzzleHttp\Psr7\Utils::tryGetContents` - -`public static function tryGetContents(resource $stream): string` - -Safely gets the contents of a given stream. - -When stream_get_contents fails, PHP normally raises a warning. This -function adds an error handler that checks for errors and throws an -exception instead. - - -## `GuzzleHttp\Psr7\Utils::uriFor` - -`public static function uriFor(string|UriInterface $uri): UriInterface` - -Returns a UriInterface for the given value. - -This function accepts a string or UriInterface and returns a -UriInterface for the given value. If the value is already a -UriInterface, it is returned as-is. - - -## `GuzzleHttp\Psr7\MimeType::fromFilename` - -`public static function fromFilename(string $filename): string|null` - -Determines the mimetype of a file by looking at its extension. - - -## `GuzzleHttp\Psr7\MimeType::fromExtension` - -`public static function fromExtension(string $extension): string|null` - -Maps a file extensions to a mimetype. - - -## Upgrading from Function API - -The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience: - -| Original Function | Replacement Method | -|----------------|----------------| -| `str` | `Message::toString` | -| `uri_for` | `Utils::uriFor` | -| `stream_for` | `Utils::streamFor` | -| `parse_header` | `Header::parse` | -| `normalize_header` | `Header::normalize` | -| `modify_request` | `Utils::modifyRequest` | -| `rewind_body` | `Message::rewindBody` | -| `try_fopen` | `Utils::tryFopen` | -| `copy_to_string` | `Utils::copyToString` | -| `copy_to_stream` | `Utils::copyToStream` | -| `hash` | `Utils::hash` | -| `readline` | `Utils::readLine` | -| `parse_request` | `Message::parseRequest` | -| `parse_response` | `Message::parseResponse` | -| `parse_query` | `Query::parse` | -| `build_query` | `Query::build` | -| `mimetype_from_filename` | `MimeType::fromFilename` | -| `mimetype_from_extension` | `MimeType::fromExtension` | -| `_parse_message` | `Message::parseMessage` | -| `_parse_request_uri` | `Message::parseRequestUri` | -| `get_message_body_summary` | `Message::bodySummary` | -| `_caseless_remove` | `Utils::caselessRemove` | - - -# Additional URI Methods - -Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, -this library also provides additional functionality when working with URIs as static methods. - -## URI Types - -An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. -An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, -the base URI. Relative references can be divided into several forms according to -[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): - -- network-path references, e.g. `//example.com/path` -- absolute-path references, e.g. `/path` -- relative-path references, e.g. `subpath` - -The following methods can be used to identify the type of the URI. - -### `GuzzleHttp\Psr7\Uri::isAbsolute` - -`public static function isAbsolute(UriInterface $uri): bool` - -Whether the URI is absolute, i.e. it has a scheme. - -### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` - -`public static function isNetworkPathReference(UriInterface $uri): bool` - -Whether the URI is a network-path reference. A relative reference that begins with two slash characters is -termed an network-path reference. - -### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` - -`public static function isAbsolutePathReference(UriInterface $uri): bool` - -Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is -termed an absolute-path reference. - -### `GuzzleHttp\Psr7\Uri::isRelativePathReference` - -`public static function isRelativePathReference(UriInterface $uri): bool` - -Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is -termed a relative-path reference. - -### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` - -`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` - -Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its -fragment component, identical to the base URI. When no base URI is given, only an empty URI reference -(apart from its fragment) is considered a same-document reference. - -## URI Components - -Additional methods to work with URI components. - -### `GuzzleHttp\Psr7\Uri::isDefaultPort` - -`public static function isDefaultPort(UriInterface $uri): bool` - -Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null -or the standard port. This method can be used independently of the implementation. - -### `GuzzleHttp\Psr7\Uri::composeComponents` - -`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` - -Composes a URI reference string from its various components according to -[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called -manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. - -### `GuzzleHttp\Psr7\Uri::fromParts` - -`public static function fromParts(array $parts): UriInterface` - -Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. - - -### `GuzzleHttp\Psr7\Uri::withQueryValue` - -`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface` - -Creates a new URI with a specific query string value. Any existing query string values that exactly match the -provided key are removed and replaced with the given key value pair. A value of null will set the query string -key without a value, e.g. "key" instead of "key=value". - -### `GuzzleHttp\Psr7\Uri::withQueryValues` - -`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` - -Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an -associative array of key => value. - -### `GuzzleHttp\Psr7\Uri::withoutQueryValue` - -`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface` - -Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the -provided key are removed. - -## Cross-Origin Detection - -`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin. - -### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` - -`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` - -Determines if a modified URL should be considered cross-origin with respect to an original URL. - -## Reference Resolution - -`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according -to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers -do when resolving a link in a website based on the current request URI. - -### `GuzzleHttp\Psr7\UriResolver::resolve` - -`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` - -Converts the relative URI into a new URI that is resolved against the base URI. - -### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` - -`public static function removeDotSegments(string $path): string` - -Removes dot segments from a path and returns the new path according to -[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4). - -### `GuzzleHttp\Psr7\UriResolver::relativize` - -`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` - -Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve(): - -```php -(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) -``` - -One use-case is to use the current request URI as base URI and then generate relative links in your documents -to reduce the document size or offer self-contained downloadable document archives. - -```php -$base = new Uri('http://example.com/a/b/'); -echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. -echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. -echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. -echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. -``` - -## Normalization and Comparison - -`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to -[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6). - -### `GuzzleHttp\Psr7\UriNormalizer::normalize` - -`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` - -Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. -This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask -of normalizations to apply. The following normalizations are available: - -- `UriNormalizer::PRESERVING_NORMALIZATIONS` - - Default normalizations which only include the ones that preserve semantics. - -- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` - - All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. - - Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` - -- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` - - Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of - ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should - not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved - characters by URI normalizers. - - Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` - -- `UriNormalizer::CONVERT_EMPTY_PATH` - - Converts the empty path to "/" for http and https URIs. - - Example: `http://example.org` → `http://example.org/` - -- `UriNormalizer::REMOVE_DEFAULT_HOST` - - Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host - "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to - RFC 3986. - - Example: `file://localhost/myfile` → `file:///myfile` - -- `UriNormalizer::REMOVE_DEFAULT_PORT` - - Removes the default port of the given URI scheme from the URI. - - Example: `http://example.org:80/` → `http://example.org/` - -- `UriNormalizer::REMOVE_DOT_SEGMENTS` - - Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would - change the semantics of the URI reference. - - Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` - -- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` - - Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes - and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization - may change the semantics. Encoded slashes (%2F) are not removed. - - Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` - -- `UriNormalizer::SORT_QUERY_PARAMETERS` - - Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be - significant (this is not defined by the standard). So this normalization is not safe and may change the semantics - of the URI. - - Example: `?lang=en&article=fred` → `?article=fred&lang=en` - -### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` - -`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool` - -Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given -`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent. -This of course assumes they will be resolved against the same base URI. If this is not the case, determination of -equivalence or difference of relative references does not mean anything. - - ## Version Guidance -| Version | Status | PHP Version | -|---------|----------------|------------------| -| 1.x | Security fixes | >=5.4,<8.1 | -| 2.x | Latest | ^7.2.5 \|\| ^8.0 | +| Version | Status | PHP Version | +|---------|--------------|--------------| +| 3.2 | Latest | >=7.4,<8.7 | +| 2.13 | Maintenance | >=7.2.5,<8.7 | +| 1.9 | End of Life | >=5.4,<8.2 | +## Quick Start + +```php +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; + +$request = new Request('GET', 'https://example.com/api'); +$response = new Response(200, ['Content-Type' => 'text/plain'], 'OK'); +$stream = Utils::streamFor('request or response body'); + +echo $request->getMethod(); +echo $response->getStatusCode(); +echo $stream; +``` + +PSR-7 messages and URIs are immutable. Methods such as `withHeader()` and +`withUri()` return a changed copy instead of modifying the original object. +Streams are mutable body handles; reading, writing, seeking, and closing a +stream can change its cursor, contents, or usability. + +```php +$request = $request->withHeader('Accept', 'application/json'); +``` + +## Documentation + +- [PSR-7 Messages](docs/psr-7-messages.md) +- [Streams and Decorators](docs/streams-and-decorators.md) +- [URI Helpers](docs/uri-helpers.md) +- [PSR-17 Factories](docs/psr-17-factories.md) +- [Message Helpers](docs/message-helpers.md) +- [Diagnostic Values](docs/diagnostic-values.md) +- [Header and Query Helpers](docs/header-and-query-helpers.md) +- [Stream Helpers](docs/stream-helpers.md) +- [URI and MIME Helpers](docs/uri-and-mime-helpers.md) +- [Upgrade Guide](UPGRADING.md) +- [Changelog](CHANGELOG.md) ## Security -If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. - +If you discover a security vulnerability within this package, please send an +email to security@tidelift.com. All security vulnerabilities will be promptly +addressed by the security maintainers listed in the +[Security Policy](https://github.com/guzzle/psr7/security/policy). Please do not +disclose security-related issues publicly until a fix has been announced. ## License -Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. - +Guzzle is made available under the MIT License (MIT). Please see +[License File](LICENSE) for more information. ## For Enterprise Available as part of the Tidelift Subscription -The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) +The maintainers of Guzzle and thousands of other packages are working with +Tidelift to deliver commercial support and maintenance for the open source +dependencies you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact dependencies you +use. +[Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/UPGRADING.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/UPGRADING.md new file mode 100644 index 000000000..7fbd4b578 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/UPGRADING.md @@ -0,0 +1,1084 @@ +Guzzle PSR-7 Upgrade Guide +========================== + +2.x to 3.0 +---------- + +Guzzle PSR-7 3.0 is a major release that raises the minimum PHP version, +updates to the PSR-7 v2 interfaces, validates header values more strictly, +preserves explicit request method casing, and rejects several invalid URI, +request, response, upload, query, stream, and multipart values that 2.x +previously accepted or cast. + +#### PHP Version and Dependencies + +Guzzle PSR-7 3.0 requires PHP `^7.4 || ^8.0`. Guzzle PSR-7 2.x supported PHP +`^7.2.5 || ^8.0`. + +If your application still supports PHP 7.2 or 7.3, continue using Guzzle PSR-7 +2.x until your minimum PHP version is raised. + +Guzzle PSR-7 3.0 requires `psr/http-message:^2.0` and +`psr/http-factory:^1.1`. Guzzle PSR-7 2.x supported +`psr/http-message:^1.1 || ^2.0` and `psr/http-factory:^1.0`. If your dependency +constraints pin `psr/http-message` to v1, update them before upgrading. + +Guzzle PSR-7 no longer depends on `ralouphie/getallheaders` and no longer +provides a transitive global `getallheaders()` polyfill. +`ServerRequest::fromGlobals()` continues to collect request headers internally. +Applications that call `getallheaders()` directly on SAPIs where PHP does not +provide it should require their own polyfill. + +#### Header Values + +Header values must now be strings or non-empty arrays of strings. Empty strings +remain valid explicit header values, but empty arrays, `null`, `false`, integers, +floats, and other non-string values are no longer cast or accepted. + +```php +// 2.x, no longer accepted in 3.0 +$response = $response->withHeader('Api-Version', 1); +$response = $response->withHeader('Empty-List', []); + +// 3.0 +$response = $response->withHeader('Api-Version', '1'); +$response = $response->withHeader('Empty-Value', ''); +``` + +Use `withoutHeader()` to remove a header. + +#### Request Method Casing + +Request methods passed explicitly to `Request`, `ServerRequest`, `withMethod()`, +`Message::parseRequest()`, and the PSR-17 factories are no longer uppercased. +PSR-7 treats method names as case-sensitive, so these APIs now preserve the +method exactly as provided. If your application requires uppercase methods, +normalize methods before constructing or modifying requests. + +`ServerRequest::fromGlobals()` is the compatibility-oriented exception. It +continues to uppercase string `REQUEST_METHOD` values read from PHP server +globals, matching Guzzle PSR-7 2.x and common server request behavior. This +normalization only applies when hydrating from globals; it does not apply to +methods passed explicitly to constructors, factories, or `withMethod()`. + +```php +// 2.x +$request = new Request('get', '/'); +$request->getMethod(); // GET + +// 3.0 +$request = new Request('get', '/'); +$request->getMethod(); // get + +// 3.0, server globals +$_SERVER['REQUEST_METHOD'] = 'post'; +$request = ServerRequest::fromGlobals(); +$request->getMethod(); // POST +``` + +#### Native PSR-7 Parameter Types + +Guzzle PSR-7 3.0 requires the argument types documented by PSR-7 more strictly. +It adds the native parameter types from `psr/http-message` v2. Code passing +invalid argument types may now receive PHP `TypeError` exceptions instead of +package-specific `InvalidArgumentException` exceptions or implicit casts. + +Native parameter type changes include: + +- `MessageInterface::withProtocolVersion()` now requires `string`. +- Message header names now require `string`. +- `RequestInterface::withRequestTarget()` and `withMethod()` now require `string`. +- `RequestInterface::withUri()` now requires `bool` for `$preserveHost`. +- `ResponseInterface::withStatus()` now requires `int` status codes and `string` reason phrases. +- Server request attribute names now require `string`. +- `UriInterface::withPort()` now requires `int|null`. +- URI scheme, user info, host, path, query, and fragment mutators now require strings. +- Stream `seek()`, `read()`, `write()`, and `getMetadata()` now require their PSR-7 v2 parameter types. +- `UploadedFileInterface::moveTo()` now requires a string target path. + +Update callers to pass values of the documented type before calling these +methods: + +```php +// 2.x, no longer supported in 3.0 +$response = $response->withStatus('201'); +$uri = $uri->withPort('8080'); + +// 3.0 +$response = $response->withStatus(201); +$uri = $uri->withPort(8080); +``` + +#### Request Modification Changes + +`Utils::modifyRequest()` now validates recognized change values before applying +request modifications. Unknown change keys are still ignored. Explicit `null` +values are no longer treated as omitted recognized changes; omit the key instead. + +Recognized change values must use the documented types: + +- `method`: `string` +- `uri`: `UriInterface` +- `query`: `string` +- `version`: `string` +- `body`: `resource|string|StreamInterface|callable|\Iterator|\Stringable` +- `set_headers`: `array>` +- `remove_headers`: `array` + +When a `uri` change contains a host, the synthesized `Host` header now +includes any non-default URI port, matching the `Request` constructor. 2.x +omitted port zero and every port on schemes other than HTTP and HTTPS. + +#### Uploaded Files + +`ServerRequestInterface::withUploadedFiles()` now rejects invalid nested upload +trees. Every leaf must be an `UploadedFileInterface` instance. + +`ServerRequest::normalizeFiles()` and `ServerRequest::fromGlobals()` now reject +malformed `$_FILES` specifications earlier. Single-file specifications must +contain non-null `tmp_name`, `size`, and `error` values. Single-file and nested +file `size` values and `error` values must be non-negative PHP integers; +numeric strings are no longer cast. If PHP supplies an upload size as a string +because the byte count cannot fit in `PHP_INT_MAX`, it is rejected rather than +truncated or cast. Nested specifications must provide `tmp_name`, `size`, and +`error` as arrays. Every key in `tmp_name` must also exist in `size` and +`error`; additional metadata entries without a matching `tmp_name` entry are +ignored. When nested `name` or `type` metadata is provided, it must also be an +array. + +If your tests or adapters build `$_FILES` arrays manually, populate the full +shape or create `UploadedFile` instances directly. + +```php +// 2.x, no longer accepted in 3.0 +$files = ['file' => ['tmp_name' => '/tmp/php123', 'error' => '0']]; + +// 3.0 +$files = ['file' => ['tmp_name' => '/tmp/php123', 'size' => 123, 'error' => UPLOAD_ERR_OK]]; +``` + +For stream-backed uploads, `UploadedFile::moveTo()` now rewinds seekable streams +before copying them. If application code reads from a seekable uploaded stream +before calling `moveTo()`, 3.0 writes the full stream contents to the target +instead of only the unread suffix. Non-seekable stream-backed uploads continue to +copy from their current position because consumed bytes cannot be replayed. + +#### Parsed Body Values + +`ServerRequestInterface::withParsedBody()` now rejects values other than +`array`, `object`, or `null`. + +```php +// 2.x, no longer accepted in 3.0 +$request = $request->withParsedBody('name=value'); + +// 3.0 +$request = $request->withParsedBody(['name' => 'value']); +``` + +#### URI Host and Scheme Validation + +URI hosts containing URI delimiters, backslashes, embedded ports passed to +`withHost()`, malformed IP-literal brackets, unbracketed IPv6, or other +malformed host forms are no longer accepted. URI schemes containing whitespace +or control characters are also no longer accepted. + +If you previously passed a host and port together to `withHost()`, split them +between `withHost()` and `withPort()`: + +```php +// 2.x, no longer accepted in 3.0 +$uri = $uri->withHost('example.com:8080'); + +// 3.0 +$uri = $uri->withHost('example.com')->withPort(8080); +``` + +Normal URI strings with ports are still supported: + +```php +$uri = new Uri('https://example.com:8080/path'); +``` + +URI parsing now accepts bracketed IPv6 and IPvFuture hosts consistently with +`withHost()` for userinfo and network-path authorities such as +`http://user@[::1]/`, `//[::1]`, and `http://[v7.a:b]/`. Invalid +delimiter-free bracketed literals, such as `[gggg::1]`, are also reported with +the intact host. Bracketed literals containing authority/path delimiters, such +as `[a@b]` or `[v1.a/b]`, still reject after fallback parsing and may report the +mangled parsed host. + +Userinfo before a bracketed IP-literal host is now percent-encoded ahead of +parsing, so raw control bytes yield encoded userinfo, such as `us%01er`, +instead of a silently mutated value, and raw DEL bytes in bracketed hosts are +rejected instead of parsed as a mutated host. Consistent with registered-name +authorities, such userinfo containing invalid UTF-8 is now rejected, +percent-sequences such as `u%41` are preserved rather than decoded, and a +literal `+` is preserved rather than decoded to a space. + +Only an optional numeric port (which may be empty) and a path, query, or +fragment may follow a bracketed IP-literal host. Trailing bytes that are +neither, such as `http://[::1]:80@evil/` or `http://[::1]:80x/`, are now +rejected instead of being reparsed into a different host. + +Parsing still URL-decodes bracketed IP-literal hosts before validation +(registered-name hosts round-trip unchanged), so a literal `+` in a bracketed +IP-literal decodes to a space and is rejected: `withHost('[v1.fe80::a+en1]')` +accepts the literal while parsing `http://[v1.fe80::a+en1]/` rejects it. +Percent-encoding inside a bracketed IP-literal is now rejected during parsing as +well, since RFC 3986 IP-literals contain no percent-encoding, so +`http://[%3A%3A1]/` no longer decodes to `[::1]`; this matches `withHost()` and +`Rfc3986::isValidHost()`. + +Percent-encoded octets in a registered-name host are normalized to uppercase +hex, so a host such as `a%c3%a9b` is represented as `a%C3%A9b`. Malformed +percent sequences and percent-encoded octets that decode to a byte forbidden in +a host, such as `ex%zz` and `%2fhost`, are rejected. + +`Uri::fromParts()` accepts integer and decimal digit string ports, but floats and +other port values are no longer cast. + +Common host forms such as `localhost`, single-label hosts, underscores, Unicode +hosts, valid IPv6 literals, and normal host and port URI strings remain +supported. + +URI schemes must now match RFC 3986 syntax and begin with a letter. + +```php +// 2.x, no longer accepted in 3.0 +$uri = (new Uri())->withScheme('0'); + +// 3.0 +$uri = (new Uri())->withScheme('https'); +``` + +The stricter validation also applies when a request is created or modified from +a custom `UriInterface` implementation and its host is used to generate or +update a `Host` header. + +`ServerRequest::getUriFromGlobals()` now falls back to `SERVER_NAME`, then +`SERVER_ADDR`, then the existing default host behavior for malformed +`HTTP_HOST` values. It also rejects zero-port `HTTP_HOST` authorities and +malformed `SERVER_PORT` values when fallback authority reconstruction needs the +server port. When `REQUEST_URI` is absolute-form or CONNECT authority-form and +supplies a valid authority, that authority is used before fallback `SERVER_PORT` +validation. Origin-form, asterisk-form, missing `REQUEST_URI`, and fallback +reconstruction paths still reject malformed `SERVER_PORT` values when fallback +authority reconstruction needs the server port. + +Absolute-form `REQUEST_URI` userinfo is removed when reconstructing the URI and +request target from globals, including empty userinfo such as +`http://@example.com/`. The host after the last raw `@` remains the URI host. + +`Message::parseRequest()` now applies 3.0 authority rules when deriving a URI +from an origin-form or asterisk-form request target. Host ports with leading +zeroes are normalized for URI reconstruction, and port zero is rejected. +It also rejects duplicate `Host` field lines, including case-insensitive +duplicates. Any present raw `Host` field is validated before returning a parsed +request, even when the request target supplies the URI authority, such as +absolute-form and CONNECT requests. Valid `Host` values may still differ from +the absolute-form or CONNECT request-target authority. + +For server globals, applications that need to reject malformed inbound `Host` +headers should validate the original server parameters before calling +`getUriFromGlobals()` or inspect them afterward. + +`Message::parseRequest()` now applies the same HTTP authority validation to +absolute-form request targets. A zero or padded-zero port +(`http://example.com:0/admin`) is rejected instead of producing a request +whose synthesized `Host` header the same parser rejects elsewhere. +Absolute-form targets with no URI host, such as `file:///etc/passwd`, are +also rejected instead of producing a hostless request URI. + +Absolute-form targets whose authority contains userinfo are also rejected, +including empty userinfo such as `http://@example.com/`. RFC 9110 deprecates +userinfo in http(s) target URIs and directs recipients to treat its presence +as an error; `Host` headers and CONNECT targets already reject it. These +rules apply to absolute-form targets of every scheme. +`ServerRequest::fromGlobals()` is unchanged and continues to strip +`REQUEST_URI` userinfo. + +URI hosts now validate percent-encoding. Malformed sequences such as `ex%zz`, +and percent-encoded octets that decode to bytes the raw host grammar already +rejects (controls, space, DEL, `/`, `?`, `#`, `@`, `\`, `:`, `[`, `]`, and `%` +itself) throw `MalformedUriException` from URI parsing and +`InvalidArgumentException` from `Uri::withHost()`, and are rejected wherever +hosts are validated, including `Host` headers and request targets in +`Message::parseRequest()`. WHATWG-conformant browsers reject all of these hosts; +curl rejects them too, except encoded DEL (`%7F`), which it decodes and forwards +to name resolution. Other percent-encoded octets, including UTF-8 data such as +`a%C3%A9b`, remain accepted and are normalized to uppercase hex. + +IPv6 hosts are now canonicalized to their RFC 5952 form when a URI is +constructed, so `getHost()`, `getAuthority()`, and `(string) $uri` return the +canonical spelling and synthesized `Host` headers use it. Leading zeros are +suppressed, hexadecimal fields are lowercase, and the longest run of two or +more zero fields is collapsed with `::`. Embedded dotted-decimal notation +follows the rendering policy of BIND-derived `inet_ntop()` implementations and +curl 8.11 and newer: exactly the IPv4-mapped (`::ffff:0:0/96`) and deprecated +IPv4-compatible (`::/96`) layouts use it, while other embedded-IPv4 forms, +including translated (NAT64) well-known prefixes such as `64:ff9b::/96` +(RFC 6052), serialize in pure hexadecimal fields. + +```php +// 2.x preserved the spelling as given +(string) new Uri('http://[0:0:0:0:0:0:0:1]/'); // http://[0:0:0:0:0:0:0:1]/ + +// 3.0 +(string) new Uri('http://[0:0:0:0:0:0:0:1]/'); // http://[::1]/ +(string) new Uri('http://[::FFFF:7F00:1]/'); // http://[::ffff:127.0.0.1]/ +(string) new Uri('http://[2001:db8:3:4::192.0.2.33]/'); // http://[2001:db8:3:4::c000:221]/ +``` + +Applications that persist URI strings, for example as cache keys, will observe +the new canonical form for previously non-canonical IPv6 spellings. Equivalent +spellings of the same address now compare as same-origin in +`UriComparator::isCrossOrigin()`, which canonicalizes bracketed IPv6 literals +from any PSR-7 implementation before comparing hosts, and as equivalent in +`UriNormalizer::isEquivalent()`. The new +`UriNormalizer::CANONICALIZE_IPV6_HOST` flag, included in the default +`UriNormalizer::PRESERVING_NORMALIZATIONS`, requests the canonical host from +other PSR-7 implementations through `withHost()` and keeps the result only +when the returned `getHost()` exactly matches the requested spelling; a +nonexact result leaves that step unchanged while other selected normalizations +still apply, and setter exceptions propagate. `UriComparator` does not share +this limitation, since it canonicalizes the extracted host text directly. The +public helper `Rfc3986::canonicalizeIpv6()` exposes the underlying +transformation. + +#### Request Host Synchronization + +`Request::withUri()` now applies PSR-7 Host header synchronization before using +the same-URI no-op shortcut. When the provided URI is the same object already +attached to the request, the method may still return a new request if the URI +has a host and the current Host header is missing, empty, or stale. + +With `$preserveHost = true`, a non-empty Host header is still preserved. Missing +or empty Host headers are treated as absent and are populated from the URI when +the URI contains a host. + +```php +$request = (new Request('GET', 'http://example.com:8124/'))->withoutHeader('Host'); + +$updated = $request->withUri($request->getUri()); + +$updated->getHeaderLine('Host'); // example.com:8124 +``` + +If your application intentionally sends an empty or stale Host header, set it +after calling `withUri()` or preserve a non-empty Host header explicitly. + +`Message::toString()` now applies the same URI host and port synthesis when +serializing a request without a `Host` header. Generated `Host` lines include +non-null URI ports. + +`Message::toString()` also validates the host it synthesizes from the request +URI and throws `InvalidArgumentException` for an invalid host, closing a header- +injection vector. This affects only a custom `UriInterface` implementation that +returns an invalid host when the request has no stored `Host` header; first- +party `Uri` instances always carry a valid host and are unaffected. + +#### URI Paths and Request Targets + +`Uri::getPath()` now normalizes multiple leading slashes to one slash when +returning the path in isolation. Casting the URI to string still preserves the +original URI representation. + +```php +$uri = new Uri('http://example.org//valid///path'); + +$uri->getPath(); // /valid///path +(string) $uri; // http://example.org//valid///path +``` + +`Request::getRequestTarget()` applies the same normalization for URI-derived +origin-form request targets. + +Reference resolution and normalization (`UriResolver`, `UriNormalizer`, and +`Uri::isSameDocumentReference()`) operate on the raw path from the URI string +form and are therefore unaffected by this normalization. + +Authority-less `file` URIs with rootless paths now serialize without the `//` +authority separator: `(string) new Uri('file:foo/bar')` returns `file:foo/bar` +instead of `file://foo/bar`, which reparses with host `foo` and path `/bar`. +Rooted paths such as `file:///myfile` keep their existing serialization. + +Authority-less `file` URIs with empty paths now serialize as `file:` instead of +`file://`, which `new Uri()` itself rejects as unparseable. This affects +degenerate URIs such as `new Uri('file:')` or +`Uri::fromParts(['scheme' => 'file'])`; the serialization of every file URI +with a non-empty path is unchanged. + +`UriResolver::removeDotSegments()` now applies RFC 3986 Section 5.2.4 to `..` +segments above the root of an absolute path: excess `..` segments no longer +consume the root, so a following empty segment is preserved. Resolving `/..//a` +against `http://example.org/base` yields `http://example.org//a` where 2.x +produced `http://example.org/a`. When the resulting URI has no authority, +`UriResolver::resolve()` and `UriNormalizer::normalize()` serialize such a +`//`-leading path with a `/.` prefix (`mailto:/.//a`), like the WHATWG URL +Standard, instead of collapsing the slashes or throwing. + +#### URI Reference Relativization + +`UriResolver::relativize()` now returns a network-path reference (for example +`//example.com`) when the target URI has the same authority as the base URI +but an empty path that no other relative reference round-trips. No path +reference can express such a target, as resolving one always produces a path +of at least `/`, and an empty reference would keep the base path or inherit +the base query or fragment. The returned reference resolves back to the +exact target string, restoring the documented round-trip guarantee for these +targets. + +```php +$base = new Uri('http://example.com/a'); +$target = new Uri('http://example.com'); + +// 2.x +(string) UriResolver::relativize($base, $target); // ../ +// which resolved back to http://example.com/ + +// 3.0 +(string) UriResolver::relativize($base, $target); // //example.com +``` + +The same applies when the base URI has a query or fragment component that an +empty relative reference would otherwise inherit. When the base URI has an +empty path as well and nothing would be inherited, shorter references such +as the empty reference, `#fragment` or `?query` are still returned. + +`relativize()` also no longer returns the empty reference when the target +path equals the base path but the base has a fragment the target lacks, as +the empty reference would reintroduce that fragment. A relative-path +reference, or a query reference when the target has a query, is returned +instead. When the relative-path reference would be a single path segment +containing a colon, which would be mistaken for a scheme name, it is +prefixed with `./` (for example `./a:b`); 2.x threw a `MalformedUriException` +for such targets when the base had a query the target lacked. + +#### HTTP Start-line Parsing + +`Message::parseRequest()` and `Message::parseResponse()` now validate HTTP +start-line fields more strictly. Malformed request methods, request targets +containing whitespace or control characters, malformed protocol versions, +invalid response status codes, invalid response spacing, and reason phrases +containing invalid control characters now throw `InvalidArgumentException`. + +`Request` and `Response` constructors and mutators apply the same validation to +protocol versions, request targets, status codes, and reason phrases. If you +parse raw HTTP messages or construct messages from partially validated input, +normalize or reject invalid values before passing them to Guzzle PSR-7. + +```php +// 2.x-style tolerant input, no longer accepted in 3.0 +Message::parseRequest("GET /foo bar HTTP/1.1\r\nHost: example.com\r\n\r\n"); +new Response(200, [], null, 'HTTP/1.1'); + +// 3.0 +Message::parseRequest("GET /foo%20bar HTTP/1.1\r\nHost: example.com\r\n\r\n"); +new Response(200, [], null, '1.1'); +``` + +`ServerRequest::fromGlobals()` applies the same validation to the +`REQUEST_METHOD` and `SERVER_PROTOCOL` server values. Malformed values that 2.x +hydrated, such as the `SERVER_PROTOCOL` value `INCLUDED` that Apache sets for +server-side include subrequests, now throw `InvalidArgumentException`. Sanitize +`$_SERVER` before calling `fromGlobals()` if such environments must be +tolerated. + +`Request::withRequestTarget('')` throws `InvalidArgumentException`; omit the +explicit request target to derive `/` or the URI-derived target automatically. + +`Message::parseMessage()` no longer unfolds folded HTTP/1.0 messages whose +start line carries control bytes in the request target; such messages now +throw the obsolete-line-folding `InvalidArgumentException`. +`Message::parseRequest()` and `Message::parseResponse()` rejected these +messages either way. + +#### Query Builder Values + +`Query::build()` now rejects unsupported values instead of relying on PHP string +casts. Query values must be scalar, `null`, stringable objects, or flat arrays of +those values. + +Nested arrays, resources, and objects without `__toString()` now throw +`InvalidArgumentException`. + +```php +// Before: could produce warnings or silently mangle the value. +Query::build(['filter' => ['name' => ['value']]]); + +// After: use explicit query keys for nested query shapes. +Query::build(['filter[name]' => 'value']); +``` + +Flat arrays are still supported for repeated query parameters: + +```php +Query::build(['tag' => ['a', 'b']]); +// tag=a&tag=b +``` + +`Uri::withQueryValues()` is stricter than `Query::build()` and requires `string` +or `null` values; cast numeric and boolean query values to string. + +#### Non-string Scalar Bodies + +`Utils::streamFor()` and message bodies no longer accept `int`, `float`, or +`bool` values. Cast them to strings first. + +```php +// 2.x, no longer accepted in 3.0 +$response = new Response(200, [], 404); + +// 3.0 +$response = new Response(200, [], '404'); +``` + +#### PumpStream Source Callables + +`PumpStream` source callables must now return a non-empty string when producing +data. Returning an empty string now throws `RuntimeException` instead of being +retried indefinitely. Return `false` or `null` to signal EOF. + +If your callable used `''` to mean "temporarily no data", update it to wait +until data is available, return a non-empty string, or return `false` or `null` +when the stream is complete. + +#### Iterator-backed Streams + +`Utils::streamFor()` now validates values yielded by `Iterator` instances before +passing them to the internal `PumpStream`. Strings, integers, finite floats, +booleans, `null`, and stringable objects are converted to string chunks. +Non-finite floats, arrays, resources, and non-stringable objects now throw +`UnexpectedValueException` when the stream is read. + +Iterator exhaustion is now the only EOF signal for iterator-backed streams. +Yielding `false`, `null`, or an empty string no longer ends the stream; those +values are zero-length chunks and are skipped while the iterator advances. If +your iterator yielded `false` or `null` to stop streaming, update it to finish +iteration instead. + +Avoid iterators that yield only zero-length chunks indefinitely. Such iterators +never produce bytes and never reach EOF, so they cannot satisfy stream reads. + +```php +// Before: yielding false or null could stop an iterator-backed stream early. +$stream = Utils::streamFor(new ArrayIterator([false, 'body'])); + +// After: false and null are skipped chunks. End the iterator to signal EOF. +$stream = Utils::streamFor(new ArrayIterator(['body'])); +``` + +#### Stream Behavior Changes + +All stream implementations now reject negative `read()` lengths with +`RuntimeException`. In 2.x, some decorators passed negative lengths through, +some returned sliced data, and some behavior varied by PHP version. + +`LimitStream` now rejects negative offsets and limits below `-1`. For +non-seekable streams, offsets are tracked by the number of bytes actually +skipped. Short reads are retried until the offset is reached, EOF is reached, or +the decorated stream stops making progress. + +`StreamWrapper` now translates `RuntimeException` failures from the wrapped +PSR-7 stream into PHP stream-wrapper failure values. When using a resource from +`StreamWrapper::getResource()`, functions such as `fread()`, `fwrite()`, +`fseek()`, `feof()`, and `fstat()` may now return normal PHP failure values +instead of propagating the PSR-7 stream exception. Call the PSR-7 stream directly +if you need exception-based failure handling. + +The `StreamWrapper::stream_read()` callback no longer declares a native return +type so read failures can return `false`. The `StreamWrapper::stream_tell()` +callback no longer declares a native return type so post-seek position lookup +failures can make `fseek()` fail. + +#### Stream Mode Capabilities + +`Stream::isReadable()` and `Stream::isWritable()` now follow PHP stream mode +semantics more closely. Update modes are detected by the presence of `+`, +including valid modes such as `rt+`, `wt+`, `at+`, `xt+`, and `ct+`. + +Literal `rw` metadata is now treated as read-only, matching PHP real-file +streams. If a custom stream wrapper previously exposed `rw` for a writable +resource, open it with a valid update mode such as `r+`, `w+`, or `a+` instead. + +#### Stream Copy Behavior + +Stream sizes, offsets, high-water marks, and byte counts are now validated as +non-negative PHP integers where applicable. Operations that would overflow +`PHP_INT_MAX` throw `OverflowException` instead of silently wrapping or producing +an invalid position or size. + +`Utils::copyToStream()` now returns the number of bytes copied and throws a +`RuntimeException` when the destination stream cannot make progress, for example +a `BufferStream` at its high-water mark or a full `DroppingStream`. Its +signature changed from `: void` to `: int`, but callers that ignore the return +value do not need to change anything. In 2.x, the copy stopped silently when the +destination could not make progress. For a guaranteed full copy, use a normal +writable stream such as a file or `php://temp` stream. + +#### Stream Timeout Detection + +Timed-out stream operations now throw +`GuzzleHttp\Psr7\Exception\TimeoutException`, which extends +`RuntimeException`. `Stream::read()`, `Stream::write()`, +`AppendStream::read()`, `CachingStream::read()`, `InflateStream::read()`, +`Utils::copyToStream()`, `Utils::copyToString()`, `Utils::hash()`, +`Utils::readLine()`, and `Utils::tryGetContents()` detect PHP-style stream +timeout metadata when a read or write operation cannot make progress. Timeout +detection is best-effort; custom stream implementations that do not expose +`timed_out` metadata continue to behave as before. Previously, timed-out reads +could be treated as EOF or return partial results, and timed-out writes could be +reported as generic write failures or no-progress writes. + +#### Message Body Summaries + +`Message::bodySummary()` still summarizes seekable bodies from the beginning, +even when the body was already partially read. It now restores the body cursor to +the position it had before the summary was created. In 2.x, calling +`bodySummary()` left seekable bodies rewound to the beginning. The optional +`$truncateAt` argument now accepts `null` as an explicit request for the default +summary length, matching the behavior of omitting the argument. + +Most applications do not need to change anything. Check your code only if you +called `bodySummary()` and then read the same body while relying on +`bodySummary()` to leave the body rewound. If you need to read the body from the +beginning after summarizing it, call `Message::rewindBody()` explicitly. + +#### Stream Lifecycle + +`FnStream` now treats `close()` and successful `detach()` calls as terminal +lifecycle operations. Its configured `close` callback is invoked at most once; +repeated `close()` calls are no-ops, destruction after explicit close no longer +invokes the close callback, and closed or detached streams no longer forward +read, write, seek, metadata, or stringification callbacks. `FnStream` also +suppresses exceptions thrown by destructor-triggered close callbacks. Call +`close()` explicitly if cleanup failures must be observed. + +`CachingStream::close()` is now idempotent. Calling `close()` after `detach()` +still closes the remote stream owned by the `CachingStream`, but it no longer +closes the detached cache resource returned to the caller. Repeated `close()` +calls are no-ops. + +`InflateStream::close()` now also closes the compressed source stream that was +passed to its constructor. In 2.x, closing an `InflateStream` left the source +stream open. Call `detach()` instead of `close()` if the compressed source +stream must stay open; `close()` after `detach()` no longer closes the source. + +`PumpStream::close()` and `PumpStream::detach()` now discard internally buffered +unread bytes. If a callable or iterator source returns more bytes than a read +requested, drain the stream before closing it if you need those buffered bytes. + +#### Multipart Part Headers and Metadata + +`MultipartStream` no longer adds default `Content-Length` headers to individual +`multipart/form-data` parts. RFC 7578 section 4.8 says multipart form-data +parts must not include `Content-*` headers other than the supported multipart +part headers, so 3.0 stops generating per-part `Content-Length` by default. + +If your tests compare raw multipart payloads, remove the generated +`Content-Length` lines from expected strings: + +```text +// 2.x generated: +--boundary\r\n +Content-Disposition: form-data; name="foo"\r\n +Content-Length: 3\r\n +\r\n +bar\r\n + +// 3.0 generates: +--boundary\r\n +Content-Disposition: form-data; name="foo"\r\n +\r\n +bar\r\n +``` + +Applications can still pass an explicit `Content-Length` header in a multipart +element's `headers` array if a non-standard peer requires it: + +```php +$body = new MultipartStream([ + [ + 'name' => 'foo', + 'contents' => 'bar', + 'headers' => ['Content-Length' => '3'], + ], +]); +``` + +`MultipartStream` now escapes generated `Content-Disposition` `name` and +`filename` parameters before serializing multipart part headers. Double quotes, +carriage returns, and line feeds are encoded as `%22`, `%0D`, and `%0A`. Literal +backslashes and other characters are serialized unchanged, matching browser +multipart form submission behavior. + +```php +// Before: these values were interpolated into the generated part header. +$body = new MultipartStream([ + [ + 'name' => "field\"\r\nname", + 'filename' => "avatar\"\r\n.txt", + 'contents' => 'body', + ], +]); + +// After: the generated Content-Disposition parameters contain +// field%22%0D%0Aname and avatar%22%0D%0A.txt. +``` + +Explicit custom boundaries are now validated using RFC 2046 multipart boundary +syntax. Omit the boundary or pass `null` to continue using a generated random +boundary. + +The string `'0'` is now treated as an explicit custom boundary and is serialized +literally. In 2.x, PHP truthiness caused `new MultipartStream($elements, '0')` +to use a generated random boundary. Omit the boundary or pass `null` when you +want a generated boundary. + +Custom multipart part header names and values are also validated before +serialization. Header names must be valid HTTP tokens, and header values must be +strings without CR, LF, or other invalid control bytes. + +`MultipartStream` now preserves trailing spaces and tabs in custom multipart +part header values when serializing the body. In 2.x, the final serialized part +header line was trimmed as a side effect of removing the generated header +terminator. Normal multipart parsers treat this optional whitespace as +insignificant, but tests, signatures, or snapshots that compare raw multipart +body bytes may need updated expectations. + +#### URI Userinfo Redaction + +`Utils::redactUserInfo()` now redacts all non-empty URI userinfo, including +username-only userinfo. In 2.x, it only redacted the password portion when +userinfo contained a password delimiter. + +```php +use GuzzleHttp\Psr7\Uri; +use GuzzleHttp\Psr7\Utils; + +// 2.x: https://TOKEN@example.com +// 3.0: https://***@example.com +(string) Utils::redactUserInfo(new Uri('https://TOKEN@example.com')); + +// 2.x: https://user:***@example.com +// 3.0: https://***@example.com +(string) Utils::redactUserInfo(new Uri('https://user:pass@example.com')); +``` + +#### Header List Helpers + +The deprecated `Header::normalize()` method was removed. Use +`Header::splitList()` to split HTTP headers that are defined as comma-separated +lists. + +`Header::splitList()` now trims list elements with spaces, horizontal tabs, +carriage returns, and line feeds. 2.x also trimmed null bytes and vertical +tabs. Validated header values cannot contain those bytes, so this only affects +strings passed to `Header::splitList()` directly. + +#### Non-instantiable Utility Classes + +Static utility and constant classes such as `Header`, `Message`, `MimeType`, +`Query`, and `Utils` now have private constructors. Replace any accidental +instantiation with static method calls or constant access. + +#### Native PHP Serialization of Streams + +Guzzle PSR-7 stream implementations no longer support native PHP `serialize()` +or `unserialize()`. Persist stream contents explicitly and recreate streams with +`Utils::streamFor()` when needed. + +#### URI Normalization of Userinfo and Host + +`UriNormalizer::CAPITALIZE_PERCENT_ENCODING` and +`UriNormalizer::DECODE_UNRESERVED_CHARACTERS` now also apply to the userinfo and +host components. In 2.x, these normalizations only rewrote the path, query, and +fragment. + +Since the host is case-insensitive and PSR-7 requires it to be lowercase, octets +decoded in the host are lowercased. Reserved percent-encoded octets such as +`%3A` are never decoded, so component boundaries cannot change, and these two +flags never modify bracketed IP-literal hosts, which only the separate +`UriNormalizer::CANONICALIZE_IPV6_HOST` normalization may canonicalize. Both +flags are part of `UriNormalizer::PRESERVING_NORMALIZATIONS`, so the output of +`UriNormalizer::normalize()` and the result of `UriNormalizer::isEquivalent()` +can change for URIs whose userinfo or host contains percent-encoded octets. +Custom `UriInterface` implementations now receive `withUserInfo()` or +`withHost()` calls from the normalizer when a normalization changes those +components; unchanged components are never rewritten. The rewrite is kept only +when the value returned by the implementation matches the normalized form, and +a userinfo with an empty user segment is never rewritten. If a setter returns a +different representation, that rewrite is discarded, while other selected +normalizations still apply, and setter exceptions propagate. No percent-encoding +normalization is applied to a component with malformed percent syntax, such as a +`%` not followed by two hexadecimal digits. + +```php +use GuzzleHttp\Psr7\Uri; +use GuzzleHttp\Psr7\UriNormalizer; + +// 2.x: http://%75ser@ex%61mple.com/ +// 3.0: http://user@example.com/ +(string) UriNormalizer::normalize(new Uri('http://%75ser@ex%61mple.com/')); +``` + +#### URI Ports and Authority Handling + +Several 3.0 changes affect how URI ports are accepted, validated, and rendered. +Each is described in its own section above: + +- `UriInterface::withPort()` now requires `int|null`; see "Native PSR-7 + Parameter Types". +- `Uri::fromParts()` validates ports instead of casting them, and `withHost()` + rejects embedded `host:port` values; see "URI Host and Scheme Validation". +- A generic `Uri` can represent ports 0 through 65535. Inbound HTTP authority + parsing is stricter: `Message::parseRequest()` rejects zero-valued ports but + accepts nonzero leading-zero ports, normalizing the reconstructed URI while + preserving the raw `Host` or request-target text; see "HTTP Start-line + Parsing" and "URI Host and Scheme Validation". +- Server globals reject a zero-valued `HTTP_HOST` and validate `SERVER_PORT` + when fallback authority reconstruction needs it. A recognized absolute-form + or CONNECT `REQUEST_URI` authority takes precedence and can still produce a + URI with port zero; see "URI Host and Scheme Validation". +- Synthesized `Host` headers now include any non-default URI port; see "Request + Modification Changes" and "Request Host Synchronization". + +`Uri` now knows the default ports of the `ws` and `wss` schemes, 80 and 443 per +RFC 6455. An explicit default port on a `ws` or `wss` URI is removed when the +URI is constructed or modified, `Uri::isDefaultPort()` returns `true` for such +URIs, and `UriNormalizer::normalize()` with the `REMOVE_DEFAULT_PORT` flag +removes the port from other `UriInterface` implementations as well. In 2.x, +these ports were preserved. + +```php +use GuzzleHttp\Psr7\Uri; + +// 2.x: ws://example.com:80/chat +// 3.0: ws://example.com/chat +(string) new Uri('ws://example.com:80/chat'); + +// 2.x: 443 +// 3.0: null +(new Uri('wss://example.com:443'))->getPort(); +``` + +Because a native `Uri` never carries a default `ws` or `wss` port, the `Host` +header synchronized from such a request URI omits the port. `Request` and +`Message` `Host` synthesis and `Utils::modifyRequest()` still append an explicit +default port that a `ws` or `wss` URI from another `UriInterface` implementation +reports. + +`UriComparator::isCrossOrigin()` now applies these default ports when comparing +effective ports, so two `ws` or `wss` URIs that differ only by an explicit +default port, such as `ws://example.com/` and `ws://example.com:80/`, are +same-origin no matter which `UriInterface` implementation supplies them. In 2.x, +such pairs were considered cross-origin. Schemes other than `http`, `https`, +`ws`, and `wss` still receive no implicit default port. + +#### Sensitive Stack Trace Arguments + +Credential-bearing URI, server-global, Authorization-header, and cookie +arguments are marked with `#[\SensitiveParameter]`. PHP 8.2 and later replace +those arguments in stack traces with `SensitiveParameterValue`. PHP 7.4 through +8.1 do not redact trace arguments. + +This does not redact logs, exception messages, object properties, wire traffic, +captured variables, return values, user callbacks, or the separate executing +object in an explicit backtrace. + +1.x to 2.0 +---------- + +Guzzle PSR-7 2.0 is a major release that removes deprecated APIs, raises the +minimum PHP version, and adds PHP 7 parameter and return types. Applications that +only depend on PSR-7 interfaces should usually need small changes. Applications +that call helper functions, extend package classes, or pass invalid argument +types need closer review. + +#### PHP Version and Dependencies + +Guzzle PSR-7 2.0 requires PHP `^7.2.5 || ^8.0`. Guzzle PSR-7 1.x supported PHP +`>=5.4.0`. + +Composer dependency changes that can affect upgrades: + +- `ralouphie/getallheaders` v2 support was dropped; 2.0 requires `^3.0`. +- `psr/http-factory:^1.0` is required because 2.0 ships PSR-17 factories through `GuzzleHttp\Psr7\HttpFactory`. + +#### PHP 7 Type Hints and Return Types + +Type hints and return types were added wherever possible. Please make sure: + +- You pass values of the documented type when calling methods and functions. +- Classes that extend Guzzle PSR-7 classes update any overridden method signatures to remain compatible. +- Code that expected package-specific `InvalidArgumentException` exceptions for invalid argument types may now receive PHP `TypeError` exceptions instead. + +Common examples include passing a real integer status code to `Response::__construct()` and passing a string method to `Request::__construct()`. + +#### Removed Function API + +The static API was introduced in 1.7.0 to mitigate problems with functions +conflicting between global and local copies of the package. The function API was +removed in 2.0.0, along with the Composer `files` autoload entry that loaded +`src/functions_include.php`. + +Replace namespaced function calls with the corresponding static methods in the +`GuzzleHttp\Psr7` namespace: + +```php +// Before: +use function GuzzleHttp\Psr7\stream_for; + +$stream = stream_for('body'); + +// After: +use GuzzleHttp\Psr7\Utils; + +$stream = Utils::streamFor('body'); +``` + +| Original Function | Replacement Method | +|-------------------|--------------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + +`Header::normalize()` remains the direct 2.0 replacement for +`normalize_header()`. In newer 2.x versions, prefer `Header::splitList()` for +new code. + +#### Deprecated URI Methods Removed + +The deprecated `Uri::resolve()` and `Uri::removeDotSegments()` methods were +removed. Use `UriResolver` instead. + +```php +// Before: +$resolved = Uri::resolve($base, '../path'); +$path = Uri::removeDotSegments('/a/../b'); + +// After: +use GuzzleHttp\Psr7\UriResolver; +use GuzzleHttp\Psr7\Utils; + +$resolved = UriResolver::resolve($base, Utils::uriFor('../path')); +$path = UriResolver::removeDotSegments('/a/../b'); +``` + +#### Stricter URI Validation + +Guzzle PSR-7 1.x automatically fixed a URI that combined an authority with a +relative path by prepending `/` to the path. That deprecated behavior was removed +in 2.0. Such URIs now throw `InvalidArgumentException`. + +```php +// Before: automatically converted to //example.com/foo. +$uri = (new Uri())->withHost('example.com')->withPath('foo'); + +// After: make the absolute path explicit. +$uri = (new Uri())->withHost('example.com')->withPath('/foo'); +``` + +#### Header Validation + +Header names are validated more strictly according to RFC 7230 token syntax. +Names containing whitespace, `/`, `(`, `)`, `\\`, or other invalid characters are +rejected. + +If you construct messages from untrusted or non-standard input, normalize or +reject invalid header names before constructing `Request`, `Response`, or +`ServerRequest` instances. + +#### Query String Boolean Serialization + +`Query::build()` now serializes booleans as `1` and `0`, matching +`http_build_query()` behavior. + +```php +Query::build(['enabled' => true, 'disabled' => false]); +// enabled=1&disabled=0 +``` + +In current 2.x versions, pass `false` as the third argument if you need textual +boolean values: + +```php +Query::build(['enabled' => true, 'disabled' => false], PHP_QUERY_RFC3986, false); +// enabled=true&disabled=false +``` + +#### Final Stream and Decorator Classes + +Several classes that were annotated with `@final` in 1.x are declared `final` in +2.0: + +- `AppendStream` +- `BufferStream` +- `CachingStream` +- `DroppingStream` +- `FnStream` +- `InflateStream` +- `LazyOpenStream` +- `LimitStream` +- `MultipartStream` +- `NoSeekStream` +- `PumpStream` +- `StreamWrapper` + +If your code extends one of these classes, replace inheritance with composition. +For custom streams, implement `Psr\Http\Message\StreamInterface` directly or use +`GuzzleHttp\Psr7\StreamDecoratorTrait` in your own class. + +`Request`, `Response`, `ServerRequest`, `Stream`, `UploadedFile`, and `Uri` remain +extendable in 2.0, but overridden methods must have compatible signatures. + +#### Public Constants and Internal Details + +Some constants that were public in 1.x are implementation details in 2.0: + +- `Stream::READABLE_MODES` +- `Stream::WRITABLE_MODES` +- `Uri::HTTP_DEFAULT_HOST` + +If your code used these constants, define application-specific constants instead +of depending on package internals. + +#### Stream Behavior Changes + +`BufferStream::write()` returns `0` instead of `false` when the buffer exceeds +its high-water mark. This keeps the method compatible with the `int` return type +from `StreamInterface::write()`. + +Several stream `__toString()` implementations now catch `Throwable`. On PHP 7.4 +and newer, exceptions thrown during stringification are rethrown. Avoid relying +on `(string) $stream` to hide read failures; call `getContents()` or `read()` and +handle exceptions when failures are possible. + +#### PSR-17 Factories + +Guzzle PSR-7 2.0 adds `GuzzleHttp\Psr7\HttpFactory`, an implementation of the +PSR-17 factory interfaces from `psr/http-factory`. This is additive, but it is +the reason for the new required dependency. + +For the full 2.0 diff, see +https://github.com/guzzle/psr7/compare/1.8.1...2.0.0. diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json b/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json index c0e7236c3..135eba57f 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/composer.json @@ -1,6 +1,7 @@ { "name": "guzzlehttp/psr7", "description": "PSR-7 message implementation that also provides common utility methods", + "license": "MIT", "keywords": [ "request", "response", @@ -11,7 +12,6 @@ "url", "psr-7" ], - "license": "MIT", "authors": [ { "name": "Graham Campbell", @@ -50,23 +50,39 @@ } ], "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^2.0", + "symfony/polyfill-php80": "^1.25", + "symfony/polyfill-php82": "^1.27" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "php-http/psr7-integration-tests": "^1.5.1", + "phpunit/phpunit": "^9.6.34" + }, + "provide": { + "psr/http-factory-implementation": "1.1", + "psr/http-message-implementation": "2.0" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, + "repositories": [ + { + "type": "package", + "package": { + "name": "jshttp/mime-db", + "version": "1.54.0.1", + "dist": { + "type": "zip", + "url": "https://codeload.github.com/jshttp/mime-db/zip/0a9fd0bfbc87a725ff638495839114e7807b7177" + } + } + } + ], "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" @@ -77,17 +93,17 @@ "GuzzleHttp\\Tests\\Psr7\\": "tests/" } }, - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "config": { "allow-plugins": { "bamarni/composer-bin-plugin": true }, "preferred-install": "dist", "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/diagnostic-values.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/diagnostic-values.md new file mode 100644 index 000000000..36f99a9f7 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/diagnostic-values.md @@ -0,0 +1,30 @@ +# Diagnostic Values + +## `GuzzleHttp\Psr7\DiagnosticValue::escape` + +`public static function escape(string $value): string` + +Escapes C0, DEL, and C1 controls as uppercase `\xNN` sequences. + +ASCII bytes from 0x20 through 0x7E and valid UTF-8 characters outside those +control ranges remain unchanged. If the input is malformed UTF-8 or PCRE cannot +process it, every byte outside printable ASCII is escaped. Valid C1 characters +are rendered as `\xNN` using their Unicode code points. During bytewise +fallback, each original byte outside printable ASCII is rendered in the same +form. The result is diagnostic text, not a reversible encoding. + +This does not encode values for HTML, JSON, shells, terminals, URLs, or protocol +fields. + +Use this helper when including an untrusted value in diagnostic text: + +```php +use GuzzleHttp\Psr7\DiagnosticValue; + +$value = "bad\nvalue"; +$message = sprintf('Invalid value: %s', DiagnosticValue::escape($value)); +// Invalid value: bad\x0Avalue +``` + +Applications must still encode the completed diagnostic for its final output +context, such as HTML or JSON. diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/header-and-query-helpers.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/header-and-query-helpers.md new file mode 100644 index 000000000..f14371213 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/header-and-query-helpers.md @@ -0,0 +1,107 @@ +# Header and Query Helpers + +This page covers helper methods for parsing structured header values, splitting +list headers, and parsing or building query strings. For basic message header +behavior, see [PSR-7 Messages](psr-7-messages.md). + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parses semicolon-separated header parameters into associative arrays, one per +comma-separated header value. Parameters without a value are appended as values +under integer keys. + +## `GuzzleHttp\Psr7\Header::splitList` + +`public static function splitList(string|string[] $header): string[]` + +Splits an HTTP header defined to contain a comma-separated list into each +individual value. Empty values are removed: + +```php +$knownEtags = Header::splitList($request->getHeader('if-none-match')); +``` + +Example headers include `accept`, `cache-control`, and `if-none-match`. + +This method must not be used to parse headers that are not defined as a list, +such as `user-agent` or `set-cookie`. + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key-value pair +becomes an array. This function does not parse nested PHP style arrays into an +associative array. For example, `foo[a]=1&foo[b]=2` will be parsed into +`['foo[a]' => '1', 'foo[b]' => '2']`. + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string` + +Build a query string from an array of key-value pairs. + +This function can use the return value of `parse()` to build a query string. +This function does not modify the provided keys when an array is encountered, +unlike `http_build_query()`. + +## `GuzzleHttp\Psr7\Utils::asciiToLower` + +`public static function asciiToLower(string $string): string` + +Converts ASCII uppercase letters in a string to lowercase. + +Unlike `strtolower()`, which honors `LC_CTYPE` before PHP 8.2, the conversion is +locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol +elements require. + +## `GuzzleHttp\Psr7\Utils::asciiToUpper` + +`public static function asciiToUpper(string $string): string` + +Converts ASCII lowercase letters in a string to uppercase. + +Unlike `strtoupper()`, which honors `LC_CTYPE` before PHP 8.2, the conversion is +locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol +elements require. + +## `GuzzleHttp\Psr7\Utils::asciiUcFirst` + +`public static function asciiUcFirst(string $string): string` + +Converts the first character of a string to uppercase when it is an ASCII +lowercase letter. + +Unlike `ucfirst()`, which honors `LC_CTYPE` before PHP 8.2, the conversion is +locale-independent and leaves every non-ASCII byte unchanged, as HTTP protocol +elements require. + +## `GuzzleHttp\Psr7\Utils::caselessContains` + +`public static function caselessContains(string $haystack, string $needle): bool` + +Checks whether the haystack contains the needle, comparing ASCII letters +case-insensitively and without locale sensitivity. + +## `GuzzleHttp\Psr7\Utils::caselessEquals` + +`public static function caselessEquals(string $left, string $right): bool` + +Checks whether two strings are equal, comparing ASCII letters case-insensitively +and without locale sensitivity. + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(array $keys, array $data): array` + +Remove the items given by the keys from the data, case-insensitively. + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Message Helpers](message-helpers.md) +- [URI Helpers](uri-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/message-helpers.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/message-helpers.md new file mode 100644 index 000000000..b560ce021 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/message-helpers.md @@ -0,0 +1,112 @@ +# Message Helpers + +This page covers static helper methods for converting, parsing, summarizing, +rewinding, and cloning PSR-7 messages. For conceptual request and response +behavior, start with [PSR-7 Messages](psr-7-messages.md). + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, ?int $truncateAt = null): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + +Reads seekable bodies from the beginning and restores the original cursor +position before returning. Pass `null` for `$truncateAt` to use the default +summary length. + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` returns a +value other than `0`. + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the `start-line` key containing the start line of the +message, `headers` key containing an associative array of header array values, +and a `body` key containing the body of the message. + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + +The URI is composed from the start-line path and the `Host` header, using +`https` when the host's port is `443` and `http` otherwise. Without a `Host` +header, only the path is returned, with extra leading slashes collapsed so an +origin-form target cannot be parsed as a network-path reference with its own +authority. An `InvalidArgumentException` is thrown when the `Host` header is +invalid. + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): RequestInterface` + +Parses a request message string into a request object. + +The request-target must be in origin form, absolute form (without a userinfo +component), authority form (`CONNECT`), or asterisk form (`OPTIONS`), and any +`Host` header must be a single valid value; otherwise an +`InvalidArgumentException` is thrown. Non-origin-form targets are preserved on +the returned request via `withRequestTarget()`. + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): ResponseInterface` + +Parses a response message string into a response object. + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate a +message. + +The changes can be one of: + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. Values must be strings or + non-empty arrays of strings. +- remove_headers: (array) Remove the given headers. Values may be strings or + integers. +- body: (mixed) Sets the given body. Present non-null values are converted with + `GuzzleHttp\Psr7\Utils::streamFor()`, including resources, streams, iterators, + callable arrays, closures, invokable objects, and stringable objects. String + inputs remain literal bodies. +- uri: (UriInterface) Set the URI. When the URI contains a host, the Host header + is updated from it, and combining this with an explicit Host entry in + set_headers throws an InvalidArgumentException. Apply an intentional Host + override separately with withHeader() afterwards. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Header and Query Helpers](header-and-query-helpers.md) +- [Stream Helpers](stream-helpers.md) +- [URI and MIME Helpers](uri-and-mime-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/psr-17-factories.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/psr-17-factories.md new file mode 100644 index 000000000..463e77610 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/psr-17-factories.md @@ -0,0 +1,33 @@ +# PSR-17 Factories + +This page explains `GuzzleHttp\Psr7\HttpFactory`, the PSR-17 factory implementation provided by this package. Use it when code expects PSR-17 factory interfaces and you want factories that create Guzzle PSR-7 messages, streams, uploaded files, and URIs. + +## `GuzzleHttp\Psr7\HttpFactory` + +`GuzzleHttp\Psr7\HttpFactory` implements all PSR-17 factory interfaces: `RequestFactoryInterface`, `ResponseFactoryInterface`, `ServerRequestFactoryInterface`, `StreamFactoryInterface`, `UploadedFileFactoryInterface`, and `UriFactoryInterface`. + +```php +use GuzzleHttp\Psr7\HttpFactory; + +$factory = new HttpFactory(); + +$request = $factory->createRequest('GET', 'https://example.com'); +$response = $factory->createResponse(200); +$serverRequest = $factory->createServerRequest('POST', '/submit', ['REMOTE_ADDR' => '192.0.2.1']); +$stream = $factory->createStream('body'); +$uri = $factory->createUri('https://example.com/path'); +``` + +It also creates streams from files and resources, and uploaded files from streams. + +```php +$stream = $factory->createStreamFromFile('/path/to/file.txt', 'r'); +$upload = $factory->createUploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'file.txt', 'text/plain'); +``` + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Streams and Decorators](streams-and-decorators.md) +- [URI Helpers](uri-helpers.md) +- [Message Helpers](message-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/psr-7-messages.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/psr-7-messages.md new file mode 100644 index 000000000..1dd4cc274 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/psr-7-messages.md @@ -0,0 +1,402 @@ +# PSR-7 Messages + +This page covers the PSR-7 message objects provided by this package: requests, responses, server requests, uploaded files, and the message-specific header, URI, and body APIs. Use these objects when you need HTTP messages that can move between Guzzle, PSR-18 clients, PSR-15 middleware, and other PSR-7 compatible libraries. + +HTTP requests and responses are both messages. A message has a start line, headers, and an optional body stream. Message and URI objects are immutable; methods named `with*()` return changed copies. Body streams are mutable handles, so reads and writes can change their cursor or contents. For body details, see [Streams and Decorators](streams-and-decorators.md). For URI helpers, see [URI Helpers](uri-helpers.md). + +## Creating Requests + +You can create a request with `GuzzleHttp\Psr7\Request`. + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', 'https://example.com/users/123'); + +// You can provide optional headers and a body. +$headers = ['Accept' => 'application/json']; +$body = 'request body'; +$request = new Request('PUT', 'https://example.com/users/123', $headers, $body); +``` + +## Creating Responses + +You can create a response with `GuzzleHttp\Psr7\Response`. + +```php +use GuzzleHttp\Psr7\Response; + +// The constructor requires no arguments. +$response = new Response(); +echo $response->getStatusCode(); +// 200 +echo $response->getProtocolVersion(); +// 1.1 + +// You can provide a status, headers, body, and protocol version. +$response = new Response(200, ['Content-Type' => 'application/json'], '{"ok":true}', '1.1'); +``` + +## Creating Server Requests + +Server requests represent incoming HTTP requests on the server side. They include the normal request method, URI, headers, and body, plus server parameters, cookies, query parameters, parsed body data, attributes, and uploaded files. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$request = new ServerRequest('POST', 'https://example.com/form', [], 'name=Guzzle', '1.1', [ + 'REMOTE_ADDR' => '192.0.2.1', +]); + +$request = $request + ->withCookieParams(['session' => 'abc']) + ->withQueryParams(['page' => '1']) + ->withParsedBody(['name' => 'Guzzle']) + ->withAttribute('route', 'profile'); + +echo $request->getServerParams()['REMOTE_ADDR']; +echo $request->getCookieParams()['session']; +echo $request->getQueryParams()['page']; +echo $request->getParsedBody()['name']; +echo $request->getAttribute('route'); +``` + +Use `ServerRequest::fromGlobals()` to create a server request from PHP superglobals. It reads `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, and `$_FILES`, and attempts to include request headers when available. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$request = ServerRequest::fromGlobals(); +``` + +Use `ServerRequest::getUriFromGlobals()` when you only need the URI derived from `$_SERVER`. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$uri = ServerRequest::getUriFromGlobals(); +``` + +For URI construction and normalization helpers, see [URI Helpers](uri-helpers.md). + +## Requests + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', 'https://example.com/users/123', [ + 'Accept' => 'application/json', +]); + +echo $request->getMethod(); +echo $request->getUri(); +``` + +PSR-7 messages are immutable. Methods such as `withHeader()` and `withUri()` return a modified copy. + +```php +$jsonRequest = $request->withHeader('Accept', 'application/json'); +``` + +## Responses + +```php +use GuzzleHttp\Psr7\Response; + +$response = new Response(200, ['Content-Type' => 'application/json'], '{"ok":true}'); + +echo $response->getStatusCode(); +echo $response->getHeaderLine('Content-Type'); +echo $response->getBody(); +``` + +## URIs + +```php +use GuzzleHttp\Psr7\Uri; + +$uri = new Uri('https://example.com/users?active=1'); + +echo $uri->getHost(); +echo $uri->getQuery(); +``` + +For URI-specific helper methods, see [URI Helpers](uri-helpers.md). + +## Headers + +Both request and response messages contain HTTP headers. + +### Accessing Headers + +You can check if a request or response has a specific header using `hasHeader()`. + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', '/', ['X-Foo' => 'bar']); + +if ($request->hasHeader('X-Foo')) { + echo 'It is there'; +} +``` + +Retrieve all header values as an array of strings with `getHeader()`. + +```php +$request->getHeader('X-Foo'); +// ['bar'] + +// Missing headers return an empty array. +$request->getHeader('X-Bar'); +// [] +``` + +Iterate over the headers of a message with `getHeaders()`. + +```php +foreach ($request->getHeaders() as $name => $values) { + echo $name . ': ' . implode(', ', $values) . "\r\n"; +} +``` + +### Complex Headers + +Some headers contain additional key-value pair information. For example, `Link` headers contain a link and additional parameters: + +```http +; rel="front"; type="image/jpeg" +``` + +Use `GuzzleHttp\Psr7\Header::parse()` to parse these headers. + +```php +use GuzzleHttp\Psr7\Header; +use GuzzleHttp\Psr7\Request; + +$request = new Request('GET', '/', [ + 'Link' => '; rel="front"; type="image/jpeg"', +]); + +$parsed = Header::parse($request->getHeader('Link')); +var_export($parsed); +``` + +This outputs: + +```php +array ( + 0 => + array ( + 0 => '', + 'rel' => 'front', + 'type' => 'image/jpeg', + ), +) +``` + +The result contains key-value pairs. Header values that have no key are indexed numerically, while header parts that form a key-value pair are added with their parameter name. + +## Body + +Request and response bodies are `Psr\Http\Message\StreamInterface` instances. Streams are used for both uploading data and downloading data. + +```php +use GuzzleHttp\Psr7\Response; + +$response = new Response(200, [], 'response body'); + +echo $response->getBody(); +// response body +``` + +The body can be cast to a string, or you can read bytes from the stream as needed. + +```php +$body = $response->getBody(); + +echo $body->read(4); +$body->seek(0); +echo $body->getContents(); +``` + +For more stream creation and decorator examples, see [Streams and Decorators](streams-and-decorators.md). + +## Uploaded Files + +Uploaded files are represented by `Psr\Http\Message\UploadedFileInterface` instances. This package provides `GuzzleHttp\Psr7\UploadedFile`, which can wrap a local file path, PHP stream resource, or PSR-7 stream. + +```php +use GuzzleHttp\Psr7\UploadedFile; +use GuzzleHttp\Psr7\Utils; + +$stream = Utils::streamFor('file contents'); +$upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'example.txt', 'text/plain'); + +echo $upload->getClientFilename(); +echo $upload->getClientMediaType(); +echo $upload->getSize(); +``` + +Call `getStream()` to read the uploaded content, or `moveTo()` to move or copy it to a target path. After `moveTo()` succeeds, `isMoved()` returns `true`, and calls that need the active upload stream will throw. + +```php +$body = $upload->getStream(); +echo $body->getContents(); + +$upload->moveTo('/path/to/target.txt'); +var_export($upload->isMoved()); +// true +``` + +If the upload error code is not `UPLOAD_ERR_OK`, the object still exposes `getError()`, `getSize()`, `getClientFilename()`, and `getClientMediaType()`, but `getStream()` and `moveTo()` throw because no successful upload content is available. + +`ServerRequest::normalizeFiles()` converts a `$_FILES`-style array into a tree of uploaded file instances. It accepts simple file specs, nested PHP `$_FILES` shapes, existing `UploadedFileInterface` instances, and nested arrays of uploaded files. + +```php +use GuzzleHttp\Psr7\ServerRequest; + +$files = ServerRequest::normalizeFiles([ + 'avatar' => [ + 'tmp_name' => '/tmp/php123', + 'size' => 1024, + 'error' => UPLOAD_ERR_OK, + 'name' => 'avatar.png', + 'type' => 'image/png', + ], + 'photos' => [ + 'tmp_name' => [ + 'first' => '/tmp/php456', + ], + 'size' => [ + 'first' => 2048, + ], + 'error' => [ + 'first' => UPLOAD_ERR_OK, + ], + 'name' => [ + 'first' => 'photo.jpg', + ], + 'type' => [ + 'first' => 'image/jpeg', + ], + ], +]); + +$request = (new ServerRequest('POST', '/upload'))->withUploadedFiles($files); +``` + +## HTTP Method Casing + +HTTP method names are case-sensitive in PSR-7. Requests created explicitly with +`Request`, `ServerRequest`, `withMethod()`, `Message::parseRequest()`, or the +PSR-17 factories preserve the method string as provided. `ServerRequest::fromGlobals()` +normalizes `$_SERVER['REQUEST_METHOD']` to uppercase for compatibility when +hydrating requests from PHP server globals. + +## Request Methods + +When creating a request, provide the HTTP method you want to perform. You can specify any method, including custom methods that are not part of RFC 9110. + +```php +use GuzzleHttp\Psr7\Request; + +$request = new Request('MOVE', 'https://example.com/resource'); + +echo $request->getMethod(); +// MOVE +``` + +## Request URI + +The request URI is represented by a `Psr\Http\Message\UriInterface` object. This package provides an implementation through `GuzzleHttp\Psr7\Uri`. + +When creating a request, you can provide the URI as a string or as a `UriInterface` instance. + +```php +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Uri; + +$request = new Request('GET', new Uri('https://example.com/users?id=123')); +``` + +### Scheme + +The [scheme](https://datatracker.ietf.org/doc/html/rfc3986#section-3.1) specifies the protocol. For HTTP requests, this is usually `http` or `https`. + +```php +$request = new Request('GET', 'https://example.com'); +echo $request->getUri()->getScheme(); +// https +``` + +### Host + +The host is accessible from the URI and is also represented by the `Host` header. + +```php +$request = new Request('GET', 'https://example.com'); +echo $request->getUri()->getHost(); +// example.com +echo $request->getHeaderLine('Host'); +// example.com +``` + +### Port + +No port is necessary for the default `http` and `https` ports. + +```php +$request = new Request('GET', 'https://example.com:8443'); +echo $request->getUri()->getPort(); +// 8443 +``` + +### Path + +The request path is accessible through the URI object. + +```php +$request = new Request('GET', 'https://example.com/users/123'); +echo $request->getUri()->getPath(); +// /users/123 +``` + +Characters that are not allowed in a URI path are percent-encoded according to [RFC 3986 section 3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3). + +### Query String + +The query string is accessible through the URI object. + +```php +$request = new Request('GET', 'https://example.com/?foo=bar'); +echo $request->getUri()->getQuery(); +// foo=bar +``` + +Characters that are not allowed in a URI query are percent-encoded according to [RFC 3986 section 3.4](https://datatracker.ietf.org/doc/html/rfc3986#section-3.4). + +## Response Status + +Responses expose the status code, reason phrase, and protocol version. + +```php +use GuzzleHttp\Psr7\Response; + +$response = new Response(200, [], 'OK'); + +echo $response->getStatusCode(); +// 200 +echo $response->getReasonPhrase(); +// OK +echo $response->getProtocolVersion(); +// 1.1 +``` + +## Related + +- [Streams and Decorators](streams-and-decorators.md) +- [URI Helpers](uri-helpers.md) +- [Message Helpers](message-helpers.md) +- [Header and Query Helpers](header-and-query-helpers.md) +- [PSR-17 Factories](psr-17-factories.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/stream-helpers.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/stream-helpers.md new file mode 100644 index 000000000..e76678eb5 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/stream-helpers.md @@ -0,0 +1,146 @@ +# Stream Helpers + +This page covers `GuzzleHttp\Psr7\Utils` helper methods for creating, copying, +hashing, reading, and safely opening PSR-7 streams. For stream implementations +and decorators, see [Streams and Decorators](streams-and-decorators.md). + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): int` + +Copy the contents of a stream into another stream until the given number of +bytes have been read, returning the number of bytes copied as an `int`. On +32-bit PHP, an unbounded copy larger than `PHP_INT_MAX` bytes cannot be +represented by that return type. 64-bit PHP is not affected. + +The destination must accept writes that make positive progress. Streams that +return 0 as a backpressure or drop signal (a `BufferStream` at its high water +mark, or a full `DroppingStream`) will cause this method to throw. For full +copies, use a normal writable stream such as a file or `php://temp` stream. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a source read or destination write cannot make +progress. + +When a source read times out, the bytes copied before it remain in the +destination. + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of bytes have +been read. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +The bytes read before a timeout are discarded, so calling this method again does +not return them. + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on PHP's +`hash_init` functions. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +The bytes read before a timeout are discarded, so calling this method again does +not return them. + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|StreamInterface|callable|\Iterator|\Stringable $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options are provided as an associative array that can contain the following +keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Yielded strings, integers, finite floats, booleans, `null`, and stringable + objects are converted to string chunks; non-finite floats and other values + throw `UnexpectedValueException` when the stream is read. Values that + stringify to an empty string are skipped while the iterator advances. + Subsequent read calls will first read from the buffer and then call `next` on + the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, the + object will be cast to a string and then a stream will be returned that uses + the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable`: When a callable array, closure, or invokable object is passed and + no earlier resource or object rule applies, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + suggested number of bytes to read. The callable can return fewer or more bytes + than requested, but MUST return a non-empty string to provide data and MUST + return `false` or `null` when there is no more data to return. Any additional + bytes will be buffered and used in subsequent reads. String inputs are always + treated as string bodies, even when they name callable functions. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +}; + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When `fopen()` fails, PHP normally raises a warning. This function adds an error +handler that checks for errors and throws an exception instead. + +## `GuzzleHttp\Psr7\Utils::tryGetContents` + +`public static function tryGetContents(resource $stream): string` + +Safely gets the contents of a given stream. + +When `stream_get_contents()` fails, PHP normally raises a warning. This function +adds an error handler that checks for errors and throws an exception instead. + +Throws `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a stream read cannot make progress. + +The bytes read before a timeout are discarded, so calling this method again does +not return them. + +## Related + +- [Streams and Decorators](streams-and-decorators.md) +- [PSR-17 Factories](psr-17-factories.md) +- [Message Helpers](message-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/streams-and-decorators.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/streams-and-decorators.md new file mode 100644 index 000000000..fd2aec759 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/streams-and-decorators.md @@ -0,0 +1,397 @@ +# Streams and Decorators + +PSR-7 request and response bodies are streams. This page covers stream creation, cursor and I/O behavior, built-in stream decorators, and wrapping PSR-7 streams as PHP resources. + +Streams allow HTTP messages to represent small strings, large files, generated data, remote resources, and other body sources through a common interface. + +The PSR-7 `Psr\Http\Message\StreamInterface` exposes methods that let consumers read, write, seek, and inspect body data without requiring the entire body to be loaded into memory. + +Streams expose their capabilities using `isReadable()`, `isWritable()`, and `isSeekable()`. These methods help collaborators determine whether a stream supports the operations they need. + +## Creating Streams + +Use `GuzzleHttp\Psr7\Utils::streamFor()` to create streams from common PHP values. It accepts strings, resources returned from `fopen()`, objects that implement `__toString()`, iterators, callable arrays, closures, invokable objects, and existing `Psr\Http\Message\StreamInterface` instances. + +Strings and `null` are stored in `php://temp` streams. PHP keeps `php://temp` data in memory until the stream exceeds 2 MB, then spills to a temporary file on disk. Non-string scalars such as integers, floats, and booleans are rejected; cast them to strings first. + +Callable sources receive a suggested read length, may return fewer or more bytes, and end the stream by returning `false` or `null`. Strings remain literal body contents, even when they name a callable. + +```php +use GuzzleHttp\Psr7\Utils; + +$stream = Utils::streamFor('string data'); +echo $stream; +// string data +echo $stream->read(3); +// str +echo $stream->getContents(); +// ing data +var_export($stream->eof()); +// true +var_export($stream->tell()); +// 11 +``` + +You can create streams from iterators. The iterator can yield any number of bytes per iteration. Any excess bytes returned by the iterator that were not requested by a stream consumer will be buffered until a subsequent read. + +```php +use GuzzleHttp\Psr7\Utils; + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield '.'; + } +}; + +$stream = Utils::streamFor($generator(1024)); +echo $stream->read(3); +// ... +``` + +## Metadata + +Streams expose stream metadata through `getMetadata()`. This method provides the data returned by PHP's [stream_get_meta_data()](https://www.php.net/manual/en/function.stream-get-meta-data.php), and can optionally expose custom metadata. + +```php +use GuzzleHttp\Psr7\Utils; + +$resource = Utils::tryFopen('/path/to/file', 'r'); +$stream = Utils::streamFor($resource); + +echo $stream->getMetadata('uri'); +// /path/to/file +var_export($stream->isReadable()); +// true +var_export($stream->isWritable()); +// false +var_export($stream->isSeekable()); +// true +``` + +## Timeouts + +Reads throw `GuzzleHttp\Psr7\Exception\TimeoutException` when PHP-style timeout +metadata can be detected after a read cannot make progress, such as on a socket +with a read timeout. + +`getContents()`, `Utils::copyToString()`, and `Utils::readLine()` discard the +bytes they read before a timeout, so calling them again does not return those +bytes. + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, then read +from it to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When the buffer reaches or exceeds 1024 bytes, it will begin returning 0 to +// writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + +By default the bytes are cached in a `php://temp` stream. You can supply your own +cache target as the second constructor argument, but it is used as a random-access +byte buffer to replay the remote stream, so it must be readable, writable, and +seekable, report an accurate position and size, and store writes losslessly. Lossy +or non-seekable streams such as `BufferStream` and `DroppingStream` are not valid +targets. + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of callables. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +appends the zlib.inflate filter, and wraps the filtered resource as a stream. + +Closing an `InflateStream` also closes the compressed source stream it decorates; `detach()` leaves the source stream open. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads from or writes to a file that is opened only after an I/O operation +takes place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +A stream that returns bytes for a streaming multipart or multipart/form-data +body when read. + +Each multipart element must contain a `name` and `contents` key. `contents` may +be any non-array value accepted by `GuzzleHttp\Psr7\Utils::streamFor()`, +including closures and invokable objects. Array contents are recursively +expanded into nested form fields. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false + +try { + $noSeek->seek(0); +} catch (\RuntimeException $e) { + echo $e->getMessage(); + // Cannot seek a NoSeekStream +} +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read-only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the suggested +number of bytes to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the `read()` method of the PumpStream. The provided callable MUST return a +non-empty string to provide data, and MUST return false or null when there is +no more data to read. Returning an empty string causes a RuntimeException +because it cannot satisfy a positive-length read. + +Userland callables that declare no parameters are tolerated by PHP, but +length-aware callables remain the recommended formal shape. + + +## Implementing Stream Decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + private $stream; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read(int $length): string + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + ($this->callback)(); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Stream Helpers](stream-helpers.md) +- [PSR-17 Factories](psr-17-factories.md) +- [URI Helpers](uri-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/uri-and-mime-helpers.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/uri-and-mime-helpers.md new file mode 100644 index 000000000..7d9a937c3 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/uri-and-mime-helpers.md @@ -0,0 +1,83 @@ +# URI and MIME Helpers + +This page covers small helper methods for redacting URI user info, converting +values into URI objects, and resolving MIME types from filenames or extensions. +For URI resolution, normalization, and comparison helpers, see +[URI Helpers](uri-helpers.md). + +## `GuzzleHttp\Psr7\Utils::redactUserInfo` + +`public static function redactUserInfo(UriInterface $uri): UriInterface` + +Redact the user info part of a URI. + +Returns the URI with the whole userinfo component replaced by `***` when one +is present, so neither the username nor the password survives into logs and +diagnostics. A URI without userinfo is returned unchanged. + +## `GuzzleHttp\Psr7\Utils::redactUserInfoInString` + +`public static function redactUserInfoInString(string $subject, string $uri): string` + +Redacts the userinfo of a raw URI string wherever it appears in a subject +string. + +The needle is taken verbatim from the raw URI rather than from parsed +components, so credentials that URI normalization would rewrite, such as raw +control bytes or unencoded reserved characters, are still found in text that +embeds the URI exactly as given, for example transport error messages. A URI +without `://` is treated as authority-form: a host and port with optional +userinfo. + +A URI that does not parse has no trustworthy authority boundary, so everything +between any scheme and its last `@` is redacted as a safe-side fallback. + +## `GuzzleHttp\Psr7\Utils::redactUriForMessage` + +`public static function redactUriForMessage(UriInterface $uri): string` + +Formats a URI for automatic diagnostics. + +The whole userinfo component is replaced by "***", and the query and +fragment are removed. The scheme, host, port, and path are preserved. +The returned string is diagnostic-escaped and the formatter never +throws. + +## `GuzzleHttp\Psr7\Utils::redactUriStringForMessage` + +`public static function redactUriStringForMessage(string $uri): string` + +Formats a raw URI for automatic diagnostics, including malformed input. + +The whole userinfo component is replaced by "***", and the query and +fragment are removed. The scheme, host, port, and path are preserved +where their boundaries can be determined safely. The returned string is +diagnostic-escaped and the formatter never throws. + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a `UriInterface` for the given value. + +This function accepts a string or `UriInterface` and returns a `UriInterface` +for the given value. If the value is already a `UriInterface`, it is returned +as-is. + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the MIME type of a file by looking at its extension. + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extension to a MIME type. + +## Related + +- [URI Helpers](uri-helpers.md) +- [Header and Query Helpers](header-and-query-helpers.md) +- [Stream Helpers](stream-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/uri-helpers.md b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/uri-helpers.md new file mode 100644 index 000000000..8052d2404 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/docs/uri-helpers.md @@ -0,0 +1,459 @@ +# URI Helpers + +This page covers this package's `Psr\Http\Message\UriInterface` implementation +and URI helper classes for classifying, composing, resolving, normalizing, +comparing, and safely modifying URIs. + +Aside from the standard `Psr\Http\Message\UriInterface` implementation provided +by the `GuzzleHttp\Psr7\Uri` class, this library also provides additional static +methods for working with URIs. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or +a relative reference. An absolute URI has a scheme. A relative reference is used +to express a URI relative to another URI, the base URI. Relative references can +be divided into several forms according to +[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins +with two slash characters is termed a network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is an absolute-path reference. A relative reference that begins +with a single slash character is termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not +begin with a slash character is termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers +to a URI that is, aside from its fragment component, identical to the base URI. +When no base URI is given, only an empty URI reference (apart from its fragment) +is considered a same-document reference. + +## URI Syntax Validation + +`GuzzleHttp\Psr7\Rfc3986` provides static methods for validating and +canonicalizing individual URI components against the grammar defined by +[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986). They operate on raw +component strings rather than on `Psr\Http\Message\UriInterface` instances. + +### `GuzzleHttp\Psr7\Rfc3986::isValidScheme` + +`public static function isValidScheme(string $scheme): bool` + +Whether the string is a valid URI scheme. Per +[RFC 3986 Section 3.1](https://datatracker.ietf.org/doc/html/rfc3986#section-3.1), +a scheme must start with a letter, followed by any number of letters, digits, +`+`, `-`, or `.`. The empty string is also accepted, since a URI reference may +omit the scheme. + +### `GuzzleHttp\Psr7\Rfc3986::isValidHost` + +`public static function isValidHost(string $host): bool` + +Whether the string is a valid URI host. Per +[RFC 3986 Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2), +the host is an IP-literal, IPv4 address, or registered name. An empty host is +accepted, since the authority, and thus the host, may be empty. Bracketed values +are validated as IPv6 or IPvFuture literals; any other value is rejected if it +contains control characters, whitespace, an authority or path delimiter (`/`, +`?`, `#`, `@`, `\`), or an embedded colon denoting a port. Percent-encoding is +validated the same way: malformed sequences (a `%` not followed by two hex +digits) and percent-encoded octets that decode to one of the rejected bytes, to +a bracket (`[`, `]`), or to `%` itself are invalid, while all other +percent-encoded octets are accepted. Rejecting these percent-encoded octets is a +deliberate guzzle host policy, stricter than the RFC 3986 `reg-name` grammar, +which permits any well-formed `pct-encoded` octet; it matches the stricter +policy used throughout the library. RFC 6874 IPv6 zone identifiers (for example +`[fe80::1%25eth0]`) are not supported. + +Registered names are otherwise intentionally permissive: single-label hosts such +as `localhost`, underscores, sub-delims, and raw or percent-encoded non-ASCII +(IDN) data are accepted and preserved as given, with no punycode conversion. +IDNA is treated as a client concern. Consumers that need DNS IDNs must perform +the conversion themselves, for example via Guzzle's `idn_conversion` request +option. + +Validity here is a statement about URI syntax only. It does not imply that the +host resolves or that a client will interpret it exactly as written. A PSR-7 URI +object does not know which resolver or transport will consume it, so it cannot +establish destination identity. Consumers that restrict the destination must +enforce that policy at the client or transport boundary and ensure the +connection uses an approved address; checking the spelling or a separate DNS +lookup is insufficient. + +### `GuzzleHttp\Psr7\Rfc3986::isValidPort` + +`public static function isValidPort(string $port): bool` + +Whether the string is a valid port number. RFC 3986 defines the port as +`*DIGIT`, which also permits an empty port and has no upper bound; this applies +the stricter policy used throughout the library instead, accepting a non-empty +run of digits (leading zeros are accepted and normalized) that resolves to a +value in the range 0-65535. + +### `GuzzleHttp\Psr7\Rfc3986::canonicalizeIpv6` + +`public static function canonicalizeIpv6(string $address): string` + +Returns the [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952#section-4) +canonical form of a valid IPv6 address. The address must be a valid textual +IPv6 address without brackets and without a zone identifier, such as the +inside of an IP-literal accepted by `isValidHost()`. Canonicalization +lowercases the hexadecimal fields, suppresses leading zeros, and collapses the +longest run of two or more zero fields (the leftmost on a tie) with `::`. +Embedded dotted-decimal notation follows the rendering policy of BIND-derived +`inet_ntop()` implementations and curl: exactly the IPv4-mapped +(`::ffff:0:0/96`) and deprecated IPv4-compatible (`::/96`) layouts use it, +while other embedded-IPv4 forms, including translated (NAT64) well-known +prefixes such as `64:ff9b::/96` (RFC 6052), serialize in pure hexadecimal +fields. An `\InvalidArgumentException` is thrown if the address cannot be +parsed. + +Validation is strict and platform-independent: the address is checked against +the RFC 3986 `IPv6address` grammar with PHP's `FILTER_VALIDATE_IP` filter and +parsed in pure PHP, so spellings that only some platform parsers accept, such as +the zero-padded dotted octets in `::ffff:192.168.001.001`, are rejected +everywhere. + +Emitting dotted-decimal notation for these two selected layouts is the +BIND-derived `inet_ntop()` and curl compatibility policy used here. +[RFC 5952 Section 5](https://datatracker.ietf.org/doc/html/rfc5952#section-5) +permits mixed notation for recognizable embedded-IPv4 prefixes but does not +limit that category to these layouts; RFC 6052, for example, defines +`64:ff9b::/96` as a Well-Known Prefix, which this implementation renders in +pure hexadecimal. The WHATWG URL Standard always emits pure hexadecimal +fields. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. +`Psr\Http\Message\UriInterface::getPort` may return null or the standard port. +This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). +Usually this method does not need to be called manually but instead is used +indirectly via `Psr\Http\Message\UriInterface::__toString`. + +PSR-7 UriInterface treats an empty component the same as a missing component as +`getQuery()`, `getFragment()` etc. always return a string. This explains the +slight difference to RFC 3986 Section 5.3. + +Another adjustment is that the authority separator is added even when the +authority is missing/empty for the "file" scheme. This is because PHP stream +functions like `file_get_contents` only work with `file:///myfile` but not with +`file:/myfile` although they are equivalent according to RFC 3986. But +`file:///` is the more common syntax for the file scheme anyway (Chrome for +example redirects to that format). The separator is omitted when such a URI has +a rootless or empty path: adding it would turn the first path segment into the +authority of the composed URI, or compose the string `file://`, which cannot be +parsed back into a URI. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of +[`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components. + +### `GuzzleHttp\Psr7\Uri::withQueryValue` + +`public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface` + +Creates a new URI with a specific query string value. Any existing query string +values that exactly match the provided key are removed and replaced with the +given key value pair. A value of null will set the query string key without a +value, e.g. "key" instead of "key=value". + +### `GuzzleHttp\Psr7\Uri::withQueryValues` + +`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface` + +Creates a new URI with multiple query string values. It has the same behavior as +`withQueryValue()` but for an associative array of key => value. + +### `GuzzleHttp\Psr7\Uri::withoutQueryValue` + +`public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface` + +Creates a new URI with a specific query string value removed. Any existing query +string values that exactly match the provided key are removed. + +## Cross-Origin Detection + +`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URI +should be considered cross-origin. + +### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` + +`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` + +Determines if a modified URI should be considered cross-origin with respect to +an original URI. + +Two URIs are cross-origin when their scheme, host, or effective port differ. +Host comparison is case-insensitive, and bracketed IPv6 literals are +canonicalized to their RFC 5952 form from any PSR-7 implementation before +comparison, so equivalent spellings of the same address are same-origin. +IPvFuture literals and bracketed values that cannot be parsed as an IPv6 +address, such as those carrying zone identifiers, still compare as +case-insensitive text. Missing ports use the default port for `http`, `https`, +`ws`, or `wss`. Other schemes do not receive implicit default ports. + +This helper only compares URI origins. It does not implement redirect handling +or credential policy. + +## Reference Resolution + +`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the +context of a base URI according to +[RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). +This is also what web browsers do when resolving a link in a document based on +the current request URI. + +### `GuzzleHttp\Psr7\UriResolver::resolve` + +`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface` + +Converts the relative URI into a new URI that is resolved against the base URI. + +When the resolved path is a relative-path reference whose first segment contains +a colon, which would be mistaken for a scheme name (RFC 3986 Section 4.2), it is +prefixed with `./`, e.g. `./a:b`. + +### `GuzzleHttp\Psr7\UriResolver::removeDotSegments` + +`public static function removeDotSegments(string $path): string` + +Removes dot segments from a path and returns the new path according to +[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4). + +Excess `..` segments above the root of an absolute path are dropped without +consuming the root, so the result can begin with `//` (e.g. `/..//a` becomes +`//a`). Such a path is not valid for a URI without an authority (RFC 3986 +Section 3.3); `resolve()` and `UriNormalizer::normalize()` serialize it with a +`/.` prefix in that case, like the WHATWG URL Standard. + +### `GuzzleHttp\Psr7\UriResolver::relativize` + +`public static function relativize(UriInterface $base, UriInterface $target): UriInterface` + +Returns the target URI as a relative reference from the base URI. This method is +the counterpart to `resolve()`: + +```php +(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) +``` + +One use case is to use the current request URI as the base URI and then generate +relative links in your documents to reduce the document size or offer +self-contained downloadable document archives. + +```php +$base = new Uri('http://example.com/a/b/'); +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. +echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. +echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. +echo UriResolver::relativize($base, new Uri('http://example.com')); // prints '//example.com'. +``` + +This method also accepts a target that is already relative and will try to +relativize it further. Only a relative-path reference will be returned as-is. + +```php +echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well +``` + +## Normalization and Comparison + +`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs +according to +[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6). + +### `GuzzleHttp\Psr7\UriNormalizer::normalize` + +`public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface` + +Returns a normalized URI. The scheme and host component are already normalized +to lowercase per PSR-7 UriInterface. This method adds additional normalizations +that can be configured with the `$flags` parameter, which is a bitmask of +normalizations to apply. + +PSR-7 UriInterface cannot distinguish between an empty component and a missing +component as `getQuery()`, `getFragment()` etc. always return a string. This +means the URIs `/?#` and `/` are treated equivalent which is not necessarily +true according to RFC 3986. But that difference is highly uncommon in reality. +So this potential normalization is implied in PSR-7 as well. + +A path the URI cannot hold, such as a `//`-leading path without an authority or +a relative-path reference whose first segment contains a colon, is prefixed with +`/.` or `./` respectively instead of throwing, as `UriResolver::resolve()` does. +The percent-encoding normalizations only do so where they rewrote the path. For +example, decoding `a%41:` yields `./aA:`, since `aA:` would be an absolute URI +with the scheme `aa`. + +The following normalizations are available: + +- `UriNormalizer::PRESERVING_NORMALIZATIONS` + + Default normalizations which only include the ones that preserve semantics. + +- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING` + + All letters within a percent-encoding triplet (e.g., "%3A") are + case-insensitive, and should be capitalized. This applies to the userinfo, + host, path, query, and fragment components. Bracketed IP-literal hosts are + skipped as a legacy tolerance for nonstandard values other implementations + may carry; zone-identifier text was briefly valid URI syntax under RFC 6874, + which RFC 9844 obsoleted and reverted. The userinfo and host are only + rewritten when the value returned by the implementation matches the + normalized form, and a userinfo with an empty user segment is never + rewritten. No percent-encoding normalization is applied to a component that + contains malformed percent syntax, such as a `%` not followed by two + hexadecimal digits. + + Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b` + +- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS` + + Decodes percent-encoded octets of unreserved characters. For consistency, + percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT + (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) + should not be created by URI producers and, when found in a URI, should be + decoded to their corresponding unreserved characters by URI normalizers. + This applies to the userinfo, host, path, query, and fragment components. + Since the host is case-insensitive and PSR-7 requires it to be lowercase, + octets decoded in the host are lowercased (e.g., "%41" becomes "a"). + Bracketed IP-literal hosts are skipped as a legacy tolerance for nonstandard + values other implementations may carry; zone-identifier text was briefly + valid URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The + userinfo and host are only rewritten when the value returned by the + implementation matches the normalized form, and a userinfo with an empty + user segment is never rewritten. No percent-encoding normalization is + applied to a component that contains malformed percent syntax, such as a `%` + not followed by two hexadecimal digits. + + Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/` + +- `UriNormalizer::CONVERT_EMPTY_PATH` + + Converts the empty path to "/" for http and https URIs. + + Example: `http://example.org` → `http://example.org/` + +- `UriNormalizer::REMOVE_DEFAULT_HOST` + + Removes the default host of the given URI scheme from the URI. Only the + "file" scheme defines the default host "localhost". All of `file:/myfile`, + `file:///myfile`, and `file://localhost/myfile` are equivalent according to + RFC 3986. + + When removing the host leaves a URI without an authority whose path begins + with `//`, the path is serialized with a `/.` prefix. + + Example: `file://localhost/myfile` → `file:///myfile`, + `file://localhost//x` → `file:///.//x` + +- `UriNormalizer::REMOVE_DEFAULT_PORT` + + Removes the default port of the given URI scheme from the URI. + + Example: `http://example.org:80/` → `http://example.org/` + +- `UriNormalizer::REMOVE_DOT_SEGMENTS` + + Removes unnecessary dot-segments. Dot-segments in relative-path references + are not removed as it would change the semantics of the URI reference. + + Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html` + +- `UriNormalizer::REMOVE_DUPLICATE_SLASHES` + + Paths which include two or more adjacent slashes are converted to one. + Webservers usually ignore duplicate slashes and treat those URIs equivalent. + But in theory those URIs do not need to be equivalent. So this normalization + may change the semantics. Encoded slashes (%2F) are not removed. + + Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html` + +- `UriNormalizer::SORT_QUERY_PARAMETERS` + + Sort query parameters with their values in alphabetical order. However, the + order of parameters in a URI may be significant (this is not defined by the + standard). So this normalization is not safe and may change the semantics of + the URI. + + Example: `?lang=en&article=fred` → `?article=fred&lang=en` + +- `UriNormalizer::CANONICALIZE_IPV6_HOST` + + Canonicalizes IPv6 hosts to their RFC 5952 form. IPv6 addresses allow + leading zeros and multiple placements of the `::` elision, so the same + address has many textual spellings. The canonical form is required for + IPv6 literals in URIs by RFC 5952 Section 6 and never changes what the URI + refers to. Native `Uri` instances already guarantee canonical output; for + other implementations, the canonical host is requested through + `withHost()` and the result is kept only when the returned `getHost()` + exactly matches the requested spelling, otherwise this step leaves the URI + unchanged while other selected normalizations still apply, and setter + exceptions propagate. + + Example: `http://[::0:0a]/` → `http://[::a]/` + +### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent` + +`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool` + +Whether two URIs can be considered equivalent. Both URIs are normalized +automatically before comparison with the given `$normalizations` bitmask. The +method also accepts relative URI references and returns true when they are +equivalent. This of course assumes they will be resolved against the same base +URI. If this is not the case, determination of equivalence or difference of +relative references does not mean anything. + +## Related + +- [PSR-7 Messages](psr-7-messages.md) +- [Streams and Decorators](streams-and-decorators.md) +- [URI and MIME Helpers](uri-and-mime-helpers.md) +- [Header and Query Helpers](header-and-query-helpers.md) diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php index cbcfaee65..3d637f8f1 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\StreamInterface; /** @@ -13,17 +14,16 @@ use Psr\Http\Message\StreamInterface; */ final class AppendStream implements StreamInterface { + use NonSerializableStreamTrait; + /** @var StreamInterface[] Streams being decorated */ - private $streams = []; + private array $streams = []; - /** @var bool */ - private $seekable = true; + private bool $seekable = true; - /** @var int */ - private $current = 0; + private int $current = 0; - /** @var int */ - private $pos = 0; + private int $pos = 0; /** * @param StreamInterface[] $streams Streams to decorate. Each stream must @@ -38,16 +38,9 @@ final class AppendStream implements StreamInterface public function __toString(): string { - try { - $this->rewind(); - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - return ''; - } + $this->rewind(); + + return $this->getContents(); } /** @@ -130,7 +123,7 @@ final class AppendStream implements StreamInterface if ($s === null) { return null; } - $size += $s; + $size = Integers::add($size, $s); } return $size; @@ -138,9 +131,9 @@ final class AppendStream implements StreamInterface public function eof(): bool { - return !$this->streams || - ($this->current >= count($this->streams) - 1 && - $this->streams[$this->current]->eof()); + return !$this->streams + || ($this->current >= count($this->streams) - 1 + && $this->streams[$this->current]->eof()); } public function rewind(): void @@ -151,7 +144,7 @@ final class AppendStream implements StreamInterface /** * Attempts to seek to the given position. Only supports SEEK_SET. */ - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { if (!$this->seekable) { throw new \RuntimeException('This AppendStream is not seekable'); @@ -167,7 +160,7 @@ final class AppendStream implements StreamInterface $stream->rewind(); } catch (\Exception $e) { throw new \RuntimeException('Unable to seek stream ' - . $i . ' of the AppendStream', 0, $e); + .$i.' of the AppendStream', 0, $e); } } @@ -182,9 +175,20 @@ final class AppendStream implements StreamInterface /** * Reads from all of the appended streams until the length is met or EOF. + * + * A timeout after some bytes were read returns those bytes, and the next + * read waits on the same stream again. */ - public function read($length): string + public function read(int $length): string { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if ($this->streams === []) { + return ''; + } + $buffer = ''; $total = count($this->streams) - 1; $remaining = $length; @@ -197,10 +201,18 @@ final class AppendStream implements StreamInterface if ($this->current === $total) { break; } - $this->current++; + ++$this->current; } - $result = $this->streams[$this->current]->read($remaining); + try { + $result = StreamTimeout::read($this->streams[$this->current], $remaining, 'Unable to read from stream: timed out'); + } catch (TimeoutException $e) { + if ($buffer === '') { + throw $e; + } + + break; + } if ($result === '') { $progressToNext = true; @@ -211,7 +223,7 @@ final class AppendStream implements StreamInterface $remaining = $length - strlen($buffer); } - $this->pos += strlen($buffer); + $this->pos = Integers::add($this->pos, strlen($buffer)); return $buffer; } @@ -231,18 +243,13 @@ final class AppendStream implements StreamInterface return $this->seekable; } - public function write($string): int + public function write(string $string): int { throw new \RuntimeException('Cannot write to an AppendStream'); } - /** - * {@inheritdoc} - * - * @return mixed - */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null): ?array { - return $key ? null : []; + return $key === null ? [] : null; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php index 21be8c0a9..68ae060bd 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -16,22 +16,22 @@ use Psr\Http\Message\StreamInterface; */ final class BufferStream implements StreamInterface { - /** @var int */ - private $hwm; + use NonSerializableStreamTrait; - /** @var string */ - private $buffer = ''; + private int $hwm; + + private string $buffer = ''; /** * @param int $hwm High water mark, representing the preferred maximum - * buffer size. If the size of the buffer exceeds the high - * water mark, then calls to write will continue to succeed - * but will return 0 to inform writers to slow down + * buffer size. If the size of the buffer reaches or exceeds + * the high water mark, then calls to write will continue to + * succeed but will return 0 to inform writers to slow down * until the buffer has been drained by reading from it. */ public function __construct(int $hwm = 16384) { - $this->hwm = $hwm; + $this->hwm = Integers::assertNonNegativeInteger($hwm, 'High water mark'); } public function __toString(): string @@ -84,7 +84,7 @@ final class BufferStream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { throw new \RuntimeException('Cannot seek a BufferStream'); } @@ -102,8 +102,12 @@ final class BufferStream implements StreamInterface /** * Reads data from the buffer. */ - public function read($length): string + public function read(int $length): string { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + $currentLength = strlen($this->buffer); if ($length >= $currentLength) { @@ -122,7 +126,7 @@ final class BufferStream implements StreamInterface /** * Writes data to the buffer. */ - public function write($string): int + public function write(string $string): int { $this->buffer .= $string; @@ -134,16 +138,14 @@ final class BufferStream implements StreamInterface } /** - * {@inheritdoc} - * * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { if ($key === 'hwm') { return $this->hwm; } - return $key ? null : []; + return $key === null ? [] : null; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php index f34722cff..4a922559d 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -13,27 +13,34 @@ use Psr\Http\Message\StreamInterface; final class CachingStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; /** @var StreamInterface Stream being wrapped */ - private $remoteStream; + private StreamInterface $remoteStream; /** @var int Number of bytes to skip reading due to a write on the buffer */ - private $skipReadBytes = 0; + private int $skipReadBytes = 0; - /** - * @var StreamInterface - */ - private $stream; + private StreamInterface $stream; + + private bool $detached = false; + + private bool $closed = false; /** * We will treat the buffer object as the body of the stream * * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream. - * @param StreamInterface $target Optionally specify where data is cached + * @param StreamInterface $target Optionally specify where data is cached. Defaults to a "php://temp" + * stream. A custom target is used as a random-access byte buffer to + * replay the remote stream, so it must be readable, writable, and + * seekable, report an accurate position and size, and store writes + * losslessly. Lossy or non-seekable streams such as BufferStream and + * DroppingStream are not valid targets. */ public function __construct( StreamInterface $stream, - StreamInterface $target = null + ?StreamInterface $target = null ) { $this->remoteStream = $stream; $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); @@ -41,6 +48,10 @@ final class CachingStream implements StreamInterface public function getSize(): ?int { + if ($this->detached) { + return null; + } + $remoteSize = $this->remoteStream->getSize(); if (null === $remoteSize) { @@ -55,18 +66,31 @@ final class CachingStream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { if ($whence === SEEK_SET) { $byte = $offset; } elseif ($whence === SEEK_CUR) { - $byte = $offset + $this->tell(); + $byte = Integers::addSigned($this->tell(), $offset); } elseif ($whence === SEEK_END) { $size = $this->remoteStream->getSize(); if ($size === null) { + // Discovering the size reads the remote stream to EOF and + // moves the cursor, so restore the cursor if the computed + // target is rejected to keep a failed seek side-effect free. + $position = $this->tell(); $size = $this->cacheEntireStream(); + + try { + $byte = Integers::addSigned($size, $offset); + } catch (\Throwable $e) { + $this->stream->seek($position); + + throw $e; + } + } else { + $byte = Integers::addSigned($size, $offset); } - $byte = $size + $offset; } else { throw new \InvalidArgumentException('Invalid whence'); } @@ -77,8 +101,16 @@ final class CachingStream implements StreamInterface // Read the remoteStream until we have read in at least the amount // of bytes requested, or we reach the end of the file. while ($diff > 0 && !$this->remoteStream->eof()) { - $this->read($diff); - $diff = $byte - $this->stream->getSize(); + $previousSize = $this->stream->getSize(); + $previousSkipReadBytes = $this->skipReadBytes; + $data = $this->read($diff); + $currentSize = $this->stream->getSize(); + + if ($data === '' && $currentSize === $previousSize && $this->skipReadBytes === $previousSkipReadBytes) { + break; + } + + $diff = $byte - $currentSize; } } else { // We can just do a normal seek since we've already seen this byte. @@ -86,8 +118,12 @@ final class CachingStream implements StreamInterface } } - public function read($length): string + public function read(int $length): string { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + // Perform a regular read on any previously read data from the buffer $data = $this->stream->read($length); $remaining = $length - strlen($data); @@ -98,8 +134,10 @@ final class CachingStream implements StreamInterface // been filled from the remote stream, then we must skip bytes on // the remote stream to emulate overwriting bytes from that // position. This mimics the behavior of other PHP stream wrappers. - $remoteData = $this->remoteStream->read( - $remaining + $this->skipReadBytes + $remoteData = StreamTimeout::read( + $this->remoteStream, + Integers::add($remaining, $this->skipReadBytes), + 'Unable to read from stream: timed out' ); if ($this->skipReadBytes) { @@ -109,21 +147,25 @@ final class CachingStream implements StreamInterface } $data .= $remoteData; - $this->stream->write($remoteData); + + // A short cache write would silently corrupt later replays, so fail loudly. + if ($this->stream->write($remoteData) !== strlen($remoteData)) { + throw new \RuntimeException('Unable to cache the entire read from the remote stream'); + } } return $data; } - public function write($string): int + public function write(string $string): int { // When appending to the end of the currently read stream, you'll want // to skip bytes from being read from the remote stream to emulate // other stream wrappers. Basically replacing bytes of data of a fixed // length. - $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + $overflow = Integers::add(strlen($string), $this->tell()) - $this->remoteStream->tell(); if ($overflow > 0) { - $this->skipReadBytes += $overflow; + $this->skipReadBytes = max($this->skipReadBytes, $overflow); } return $this->stream->write($string); @@ -134,13 +176,57 @@ final class CachingStream implements StreamInterface return $this->stream->eof() && $this->remoteStream->eof(); } + public function detach() + { + if ($this->detached) { + return null; + } + + $position = $this->tell(); + + $this->cacheEntireStream(); + $this->stream->seek($position); + + $resource = $this->stream->detach(); + $this->detached = true; + + return $resource; + } + /** - * Close both the remote stream and buffer stream + * Close the remote stream and any attached cache stream. */ public function close(): void { - $this->remoteStream->close(); - $this->stream->close(); + if ($this->closed) { + return; + } + + $closeCache = !$this->detached; + $this->closed = true; + $this->detached = true; + + $exception = null; + + try { + $this->remoteStream->close(); + } catch (\Throwable $e) { + $exception = $e; + } + + if ($closeCache) { + try { + $this->stream->close(); + } catch (\Throwable $e) { + if ($exception === null) { + $exception = $e; + } + } + } + + if ($exception !== null) { + throw $exception; + } } private function cacheEntireStream(): int diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DiagnosticValue.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DiagnosticValue.php new file mode 100644 index 000000000..151aa9f73 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DiagnosticValue.php @@ -0,0 +1,63 @@ += 0x20 && $byte <= 0x7E) { + $escaped .= $value[$offset]; + + continue; + } + + $escaped .= \sprintf('\\x%02X', $byte); + } + + return $escaped; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php index 6e3d209d0..3a84ef52a 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -13,12 +13,11 @@ use Psr\Http\Message\StreamInterface; final class DroppingStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var int */ - private $maxLength; + private int $maxLength; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; /** * @param StreamInterface $stream Underlying stream to decorate. @@ -27,10 +26,10 @@ final class DroppingStream implements StreamInterface public function __construct(StreamInterface $stream, int $maxLength) { $this->stream = $stream; - $this->maxLength = $maxLength; + $this->maxLength = Integers::assertNonNegativeInteger($maxLength, 'Maximum length'); } - public function write($string): int + public function write(string $string): int { $diff = $this->maxLength - $this->stream->getSize(); diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php new file mode 100644 index 000000000..f8ed545b5 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Exception/TimeoutException.php @@ -0,0 +1,14 @@ + */ - private $methods; + private array $methods; + + private bool $detached = false; /** * @param array $methods Hash of method name to a callable. @@ -31,9 +35,9 @@ final class FnStream implements StreamInterface { $this->methods = $methods; - // Create the functions on the class + // Create the callables on the class foreach ($methods as $name => $fn) { - $this->{'_fn_' . $name} = $fn; + $this->{'_fn_'.$name} = $fn; } } @@ -44,8 +48,7 @@ final class FnStream implements StreamInterface */ public function __get(string $name): void { - throw new \BadMethodCallException(str_replace('_fn_', '', $name) - . '() is not implemented in the FnStream'); + throw new \BadMethodCallException(\sprintf('%s() is not implemented in the FnStream', DiagnosticValue::escape(str_replace('_fn_', '', $name)))); } /** @@ -53,8 +56,14 @@ final class FnStream implements StreamInterface */ public function __destruct() { - if (isset($this->_fn_close)) { - call_user_func($this->_fn_close); + if ($this->detached || !isset($this->_fn_close)) { + return; + } + + try { + $this->close(); + } catch (\Throwable $e) { + // Destructors must not surface cleanup failures. } } @@ -65,7 +74,18 @@ final class FnStream implements StreamInterface */ public function __wakeup(): void { - throw new \LogicException('FnStream should never be unserialized'); + $this->methods = []; + $this->detached = true; + + throw new \LogicException(static::class.' should never be unserialized'); + } + + public function __unserialize(array $data): void + { + $this->methods = []; + $this->detached = true; + + throw new \LogicException(static::class.' should never be unserialized'); } /** @@ -73,11 +93,9 @@ final class FnStream implements StreamInterface * specific method calls. * * @param StreamInterface $stream Stream to decorate - * @param array $methods Hash of method name to a closure - * - * @return FnStream + * @param array $methods Hash of method name to a callable */ - public static function decorate(StreamInterface $stream, array $methods) + public static function decorate(StreamInterface $stream, array $methods): self { // If any of the required methods were not provided, then simply // proxy to the decorated stream. @@ -92,89 +110,141 @@ final class FnStream implements StreamInterface public function __toString(): string { - try { - return call_user_func($this->_fn___toString); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - return ''; - } + $this->assertAttached(); + + /** @var string */ + return ($this->_fn___toString)(); } public function close(): void { - call_user_func($this->_fn_close); + if ($this->detached) { + return; + } + + $close = $this->_fn_close; + $this->detached = true; + $close(); } public function detach() { - return call_user_func($this->_fn_detach); + if ($this->detached) { + return null; + } + + $detach = $this->_fn_detach; + $result = $detach(); + $this->detached = true; + + return $result; } public function getSize(): ?int { - return call_user_func($this->_fn_getSize); + if ($this->detached) { + return null; + } + + return ($this->_fn_getSize)(); } public function tell(): int { - return call_user_func($this->_fn_tell); + $this->assertAttached(); + + return ($this->_fn_tell)(); } public function eof(): bool { - return call_user_func($this->_fn_eof); + $this->assertAttached(); + + return ($this->_fn_eof)(); } public function isSeekable(): bool { - return call_user_func($this->_fn_isSeekable); + if ($this->detached) { + return false; + } + + return ($this->_fn_isSeekable)(); } public function rewind(): void { - call_user_func($this->_fn_rewind); + $this->assertAttached(); + + ($this->_fn_rewind)(); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - call_user_func($this->_fn_seek, $offset, $whence); + $this->assertAttached(); + + ($this->_fn_seek)($offset, $whence); } public function isWritable(): bool { - return call_user_func($this->_fn_isWritable); + if ($this->detached) { + return false; + } + + return ($this->_fn_isWritable)(); } - public function write($string): int + public function write(string $string): int { - return call_user_func($this->_fn_write, $string); + $this->assertAttached(); + + return ($this->_fn_write)($string); } public function isReadable(): bool { - return call_user_func($this->_fn_isReadable); + if ($this->detached) { + return false; + } + + return ($this->_fn_isReadable)(); } - public function read($length): string + public function read(int $length): string { - return call_user_func($this->_fn_read, $length); + $this->assertAttached(); + + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + return ($this->_fn_read)($length); } public function getContents(): string { - return call_user_func($this->_fn_getContents); + $this->assertAttached(); + + return ($this->_fn_getContents)(); } /** - * {@inheritdoc} - * * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - return call_user_func($this->_fn_getMetadata, $key); + if ($this->detached) { + return $key === null ? [] : null; + } + + return ($this->_fn_getMetadata)($key); + } + + private function assertAttached(): void + { + if ($this->detached) { + throw new \RuntimeException('Stream is detached'); + } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php index 4d7005b22..52583c7bf 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Header.php @@ -6,11 +6,14 @@ namespace GuzzleHttp\Psr7; final class Header { + private function __construct() + { + } + /** - * Parse an array of header values containing ";" separated data into an - * array of associative arrays representing the header key value pair data - * of the header. When a parameter does not contain a value, but just - * contains a key, this function will inject a key with a '' string value. + * Parses semicolon-separated header parameters into associative arrays, one + * per comma-separated header value. Parameters without a value are appended + * as values under integer keys. * * @param string|array $header Header to parse into components. */ @@ -22,8 +25,14 @@ final class Header foreach ((array) $header as $value) { foreach (self::splitList($value) as $val) { $part = []; - foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { - if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + foreach (self::splitParameters($val) as $kvp) { + $count = preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches); + + if ($count === false) { + throw new \RuntimeException('Unable to parse header parameters: '.preg_last_error_msg()); + } + + if ($count !== 0) { $m = $matches[0]; if (isset($m[1])) { $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); @@ -42,35 +51,60 @@ final class Header } /** - * Converts an array of header values that may contain comma separated - * headers into an array of headers with no comma separated values. + * Split a header value into semicolon-separated parameters. * - * @param string|array $header Header to normalize. - * - * @deprecated Use self::splitList() instead. + * @return string[] */ - public static function normalize($header): array + private static function splitParameters(string $value): array { - $result = []; - foreach ((array) $header as $value) { - foreach (self::splitList($value) as $parsed) { - $result[] = $parsed; + $values = []; + $start = 0; + $isQuoted = false; + $isEscaped = false; + + for ($i = 0, $max = \strlen($value); $i < $max; ++$i) { + $char = $value[$i]; + + if ($isEscaped) { + $isEscaped = false; + + continue; + } + + if ($isQuoted && $char === '\\') { + $isEscaped = true; + + continue; + } + + if ($char === '"') { + $isQuoted = !$isQuoted; + + continue; + } + + if (!$isQuoted && $char === ';') { + $values[] = \substr($value, $start, $i - $start); + $start = $i + 1; } } - return $result; + $values[] = \substr($value, $start); + + return $values; } /** - * Splits a HTTP header defined to contain a comma-separated list into - * each individual value. Empty values will be removed. + * Splits an HTTP header defined to contain a comma-separated list into each + * individual value. Empty values are removed. * - * Example headers include 'accept', 'cache-control' and 'if-none-match'. + * Example headers include `accept`, `cache-control`, and `if-none-match`. * - * This method must not be used to parse headers that are not defined as - * a list, such as 'user-agent' or 'set-cookie'. + * This method must not be used to parse headers that are not defined as a + * list, such as `user-agent` or `set-cookie`. * - * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * @param string|string[] $values Header value as returned by + * MessageInterface::getHeader() * * @return string[] */ @@ -89,7 +123,7 @@ final class Header $v = ''; $isQuoted = false; $isEscaped = false; - for ($i = 0, $max = \strlen($value); $i < $max; $i++) { + for ($i = 0, $max = \strlen($value); $i < $max; ++$i) { if ($isEscaped) { $v .= $value[$i]; $isEscaped = false; @@ -98,7 +132,7 @@ final class Header } if (!$isQuoted && $value[$i] === ',') { - $v = \trim($v); + $v = \trim($v, " \t\n\r"); if ($v !== '') { $result[] = $v; } @@ -123,7 +157,7 @@ final class Header $v .= $value[$i]; } - $v = \trim($v); + $v = \trim($v, " \t\n\r"); if ($v !== '') { $result[] = $v; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php index 30be222fc..a515fc38f 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -23,26 +23,26 @@ use Psr\Http\Message\UriInterface; * Note: in consuming code it is recommended to require the implemented interfaces * and inject the instance of this class multiple times. */ -final class HttpFactory implements - RequestFactoryInterface, - ResponseFactoryInterface, - ServerRequestFactoryInterface, - StreamFactoryInterface, - UploadedFileFactoryInterface, - UriFactoryInterface +final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface { public function createUploadedFile( StreamInterface $stream, - int $size = null, + ?int $size = null, int $error = \UPLOAD_ERR_OK, - string $clientFilename = null, - string $clientMediaType = null + ?string $clientFilename = null, + ?string $clientMediaType = null ): UploadedFileInterface { if ($size === null) { $size = $stream->getSize(); } - return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + return new UploadedFile( + $stream, + Integers::assertOptionalNonNegativeSize($size, 'Uploaded file size'), + $error, + $clientFilename, + $clientMediaType + ); } public function createStream(string $content = ''): StreamInterface @@ -56,7 +56,7 @@ final class HttpFactory implements $resource = Utils::tryFopen($file, $mode); } catch (\RuntimeException $e) { if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { - throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + throw new \InvalidArgumentException(sprintf('Invalid file opening mode: %s', DiagnosticValue::escape($mode)), 0, $e); } throw $e; @@ -70,8 +70,12 @@ final class HttpFactory implements return Utils::streamFor($resource); } - public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface - { + public function createServerRequest( + string $method, + $uri, + #[\SensitiveParameter] + array $serverParams = [] + ): ServerRequestInterface { if (empty($method)) { if (!empty($serverParams['REQUEST_METHOD'])) { $method = $serverParams['REQUEST_METHOD']; diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php index 8e00f1c32..784e40c20 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\StreamInterface; /** @@ -13,25 +14,116 @@ use Psr\Http\Message\StreamInterface; * then appends the zlib.inflate filter. The stream is then converted back * to a Guzzle stream resource to be used as a Guzzle stream. * - * @link http://tools.ietf.org/html/rfc1950 - * @link http://tools.ietf.org/html/rfc1952 - * @link http://php.net/manual/en/filters.compression.php + * @see https://datatracker.ietf.org/doc/html/rfc1950 + * @see https://datatracker.ietf.org/doc/html/rfc1952 + * @see https://www.php.net/manual/en/filters.compression.php */ final class InflateStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; + + private ?StreamInterface $source; + + private ?ReadFailureCapturingStream $capturingSource; public function __construct(StreamInterface $stream) { - $resource = StreamWrapper::getResource($stream); + $this->source = $stream; + $this->capturingSource = new ReadFailureCapturingStream($stream); + $resource = StreamWrapper::getResource($this->capturingSource); // Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data - // See http://www.zlib.net/manual.html#Advanced definition of inflateInit2 + // See https://www.zlib.net/manual.html#Advanced definition of inflateInit2 // "Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection" // Default window size is 15. stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]); $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); } + + public function eof(): bool + { + return ($this->capturingSource === null || !$this->capturingSource->hasReadFailure()) && $this->stream->eof(); + } + + public function read(int $length): string + { + if ($length <= 0 || $this->source === null || $this->capturingSource === null) { + return $this->stream->read($length); + } + + // A source failure that follows decoded bytes is reported by the next read. + $failure = $this->capturingSource->takeReadFailure(); + + if ($failure === null) { + try { + $data = $this->stream->read($length); + } catch (TimeoutException $e) { + throw $e; + } catch (\RuntimeException $e) { + if (StreamTimeout::isReadTimedOut($this->source)) { + throw new TimeoutException('Unable to read from stream: timed out', 0, $e); + } + + throw $e; + } + + if ($data !== '') { + return $data; + } + + $failure = $this->capturingSource->takeReadFailure(); + } + + if ($failure instanceof TimeoutException || StreamTimeout::isReadTimedOut($this->source)) { + throw new TimeoutException('Unable to read from stream: timed out', 0, $failure); + } + + if ($failure !== null) { + throw new \RuntimeException('Unable to read from stream', 0, $failure); + } + + return ''; + } + + public function close(): void + { + $source = $this->source; + $this->source = null; + $this->capturingSource = null; + + $exception = null; + + try { + $this->stream->close(); + } catch (\Throwable $e) { + $exception = $e; + } + + if ($source !== null) { + try { + $source->close(); + } catch (\Throwable $e) { + if ($exception === null) { + $exception = $e; + } + } + } + + if ($exception !== null) { + throw $exception; + } + } + + public function detach() + { + $this->source = null; + if ($this->capturingSource !== null) { + $this->capturingSource->stopCapturing(); + $this->capturingSource = null; + } + + return $this->stream->detach(); + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Integers.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Integers.php new file mode 100644 index 000000000..7eead6468 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Integers.php @@ -0,0 +1,104 @@ + \PHP_INT_MAX - $a) { + throw new \OverflowException('Stream byte count exceeds the maximum integer size supported on this platform'); + } + + return $a + $b; + } + + public static function addSigned(int $base, int $delta): int + { + if ($base < 0) { + throw new \InvalidArgumentException('Stream offset must be non-negative'); + } + + if ($delta > 0 && $delta > \PHP_INT_MAX - $base) { + throw new \OverflowException('Stream offset exceeds the maximum integer size supported on this platform'); + } + + $value = $base + $delta; + if ($value < 0) { + // A negative computed offset is a seek failure at runtime, so throw + // RuntimeException per PSR-7, unlike the precondition check above. + throw new \RuntimeException('Stream offset must be non-negative'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertEngineInteger($value, string $what): ?int + { + if ($value === false || $value === null) { + return null; + } + + if (!\is_int($value) || $value < 0) { + throw new \OverflowException($what.' exceeds the maximum integer size supported on this platform'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertOptionalNonNegativeSize($value, string $name): ?int + { + if ($value === null) { + return null; + } + + if (!\is_int($value) || $value < 0) { + throw new \InvalidArgumentException($name.' must be a non-negative integer or null'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertNonNegativeInteger($value, string $name): int + { + if (!\is_int($value) || $value < 0) { + throw new \InvalidArgumentException($name.' must be a non-negative integer'); + } + + return $value; + } + + /** + * @param mixed $value + */ + public static function assertLimitInteger($value, string $name): int + { + if (!\is_int($value) || $value < -1) { + throw new \InvalidArgumentException($name.' must be -1 or a non-negative integer'); + } + + return $value; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php index f6c84904e..06e0849ee 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -13,17 +13,13 @@ use Psr\Http\Message\StreamInterface; final class LazyOpenStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var string */ - private $filename; + private string $filename; - /** @var string */ - private $mode; + private string $mode; - /** - * @var StreamInterface - */ - private $stream; + private StreamInterface $stream; /** * @param string $filename File to lazily open @@ -39,6 +35,13 @@ final class LazyOpenStream implements StreamInterface unset($this->stream); } + public function __unserialize(array $data): void + { + $this->stream = new BufferStream(); + + throw new \LogicException(static::class.' should never be unserialized'); + } + /** * Creates the underlying stream lazily when required. */ diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php index fb2232557..03887b32a 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -12,15 +12,15 @@ use Psr\Http\Message\StreamInterface; final class LimitStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; /** @var int Offset to start reading from */ - private $offset; + private int $offset; /** @var int Limit the number of bytes that can be read */ - private $limit; + private int $limit; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; /** * @param StreamInterface $stream Stream to wrap @@ -51,7 +51,7 @@ final class LimitStream implements StreamInterface return false; } - return $this->stream->tell() >= $this->offset + $this->limit; + return $this->stream->tell() >= Integers::add($this->offset, $this->limit); } /** @@ -61,17 +61,21 @@ final class LimitStream implements StreamInterface { if (null === ($length = $this->stream->getSize())) { return null; - } elseif ($this->limit === -1) { - return $length - $this->offset; - } else { - return min($this->limit, $length - $this->offset); } + + $size = $length - $this->offset; + + if ($this->limit !== -1) { + $size = min($this->limit, $size); + } + + return max(0, $size); } /** * Allow for a bounded seek on the read limited stream */ - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { if ($whence !== SEEK_SET || $offset < 0) { throw new \RuntimeException(sprintf( @@ -81,11 +85,12 @@ final class LimitStream implements StreamInterface )); } - $offset += $this->offset; + $offset = Integers::add($this->offset, $offset); if ($this->limit !== -1) { - if ($offset > $this->offset + $this->limit) { - $offset = $this->offset + $this->limit; + $upperBound = Integers::add($this->offset, $this->limit); + if ($offset > $upperBound) { + $offset = $upperBound; } } @@ -109,17 +114,48 @@ final class LimitStream implements StreamInterface */ public function setOffset(int $offset): void { + $offset = Integers::assertNonNegativeInteger($offset, 'Offset'); + $current = $this->stream->tell(); - if ($current !== $offset) { - // If the stream cannot seek to the offset position, then read to it - if ($this->stream->isSeekable()) { - $this->stream->seek($offset); - } elseif ($current > $offset) { - throw new \RuntimeException("Could not seek to stream offset $offset"); - } else { - $this->stream->read($offset - $current); + if ($current === $offset) { + $this->offset = $offset; + + return; + } + + // If the stream cannot seek to the offset position, then read to it. + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + $this->offset = $offset; + + return; + } + + if ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset $offset"); + } + + while ($current < $offset) { + if ($this->stream->eof()) { + $this->offset = $current; + + return; } + + $result = $this->stream->read($offset - $current); + + if ($result === '') { + if ($this->stream->eof()) { + $this->offset = $current; + + return; + } + + throw new \RuntimeException("Could not seek to stream offset $offset"); + } + + $current = Integers::add($current, strlen($result)); } $this->offset = $offset; @@ -134,18 +170,22 @@ final class LimitStream implements StreamInterface */ public function setLimit(int $limit): void { - $this->limit = $limit; + $this->limit = Integers::assertLimitInteger($limit, 'Limit'); } - public function read($length): string + public function read(int $length): string { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + if ($this->limit === -1) { return $this->stream->read($length); } // Check if the current position is less than the total allowed // bytes + original offset - $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + $remaining = Integers::add($this->offset, $this->limit) - $this->stream->tell(); if ($remaining > 0) { // Only return the amount of requested data, ensuring that the byte // limit is not exceeded diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php index c1e15f826..5158f692b 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Message.php @@ -7,9 +7,16 @@ namespace GuzzleHttp\Psr7; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; final class Message { + private const DEFAULT_BODY_SUMMARY_TRUNCATE_AT = 120; + + private function __construct() + { + } + /** * Returns the string representation of an HTTP message. * @@ -18,31 +25,48 @@ final class Message public static function toString(MessageInterface $message): string { if ($message instanceof RequestInterface) { - $msg = trim($message->getMethod() . ' ' - . $message->getRequestTarget()) - . ' HTTP/' . $message->getProtocolVersion(); + $msg = trim($message->getMethod().' ' + .$message->getRequestTarget(), " \n\r\t\0\x0B") + .' HTTP/'.$message->getProtocolVersion(); if (!$message->hasHeader('host')) { - $msg .= "\r\nHost: " . $message->getUri()->getHost(); + $msg .= "\r\nHost: ".self::hostHeaderFromUri($message->getUri()); } } elseif ($message instanceof ResponseInterface) { - $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' - . $message->getStatusCode() . ' ' - . $message->getReasonPhrase(); + $msg = 'HTTP/'.$message->getProtocolVersion().' ' + .$message->getStatusCode().' ' + .$message->getReasonPhrase(); } else { throw new \InvalidArgumentException('Unknown message type'); } foreach ($message->getHeaders() as $name => $values) { - if (strtolower($name) === 'set-cookie') { + if (is_string($name) && Utils::asciiToLower($name) === 'set-cookie') { foreach ($values as $value) { - $msg .= "\r\n{$name}: " . $value; + $msg .= "\r\n{$name}: ".$value; } } else { - $msg .= "\r\n{$name}: " . implode(', ', $values); + $msg .= "\r\n{$name}: ".implode(', ', $values); } } - return "{$msg}\r\n\r\n" . $message->getBody(); + return "{$msg}\r\n\r\n".$message->getBody(); + } + + private static function hostHeaderFromUri(UriInterface $uri): string + { + $host = $uri->getHost(); + + if ($host === '') { + return ''; + } + + Uri::assertValidHost($host); + + if (($port = $uri->getPort()) !== null) { + $host .= ':'.$port; + } + + return $host; } /** @@ -50,11 +74,17 @@ final class Message * * Will return `null` if the response is not printable. * + * Reads seekable bodies from the beginning and restores the original cursor + * position before returning. Pass `null` for `$truncateAt` to use the + * default summary length. + * * @param MessageInterface $message The message to get the body summary - * @param int $truncateAt The maximum allowed size of the summary + * @param int|null $truncateAt Maximum allowed size of the summary */ - public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + public static function bodySummary(MessageInterface $message, ?int $truncateAt = null): ?string { + $truncateAt ??= self::DEFAULT_BODY_SUMMARY_TRUNCATE_AT; + $body = $message->getBody(); if (!$body->isSeekable() || !$body->isReadable()) { @@ -67,12 +97,21 @@ final class Message return null; } - $body->rewind(); - $summary = $body->read($truncateAt); - $body->rewind(); + $position = $body->tell(); - if ($size > $truncateAt) { - $summary .= ' (truncated...)'; + try { + $body->rewind(); + $summary = $body->read($truncateAt); + + if ($size > $truncateAt) { + if (preg_match('//u', $summary) !== 1) { + $summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3)); + } + + $summary .= ' (truncated...)'; + } + } finally { + $body->seek($position); } // Matches any printable character, including unicode characters: @@ -84,6 +123,60 @@ final class Message return $summary; } + /** + * Trims a partial UTF-8 character from the end of a truncated string. + */ + private static function trimTrailingIncompleteUtf8Character(string $summary, string $lookahead): string + { + $length = strlen($summary); + + if ($length === 0) { + return $summary; + } + + $start = $length - 1; + + while ($start >= 0) { + $byte = ord($summary[$start]); + + if ($byte < 0x80 || $byte > 0xBF) { + break; + } + + --$start; + } + + if ($start < 0) { + return $summary; + } + + $lead = ord($summary[$start]); + + if ($lead >= 0xC2 && $lead <= 0xDF) { + $expectedLength = 2; + } elseif ($lead >= 0xE0 && $lead <= 0xEF) { + $expectedLength = 3; + } elseif ($lead >= 0xF0 && $lead <= 0xF4) { + $expectedLength = 4; + } else { + return $summary; + } + + $availableLength = $length - $start; + + if ($availableLength >= $expectedLength) { + return $summary; + } + + $sequence = substr($summary, $start).substr($lookahead, 0, $expectedLength - $availableLength); + + if (strlen($sequence) !== $expectedLength || preg_match('//u', $sequence) !== 1) { + return $summary; + } + + return substr($summary, 0, $start); + } + /** * Attempts to rewind a message body and throws an exception on failure. * @@ -106,117 +199,49 @@ final class Message /** * Parses an HTTP message into an associative array. * - * The array contains the "start-line" key containing the start line of - * the message, "headers" key containing an associative array of header - * array values, and a "body" key containing the body of the message. + * The array contains the `start-line` key containing the start line of the + * message, `headers` key containing an associative array of header array + * values, and a `body` key containing the body of the message. * * @param string $message HTTP request or response to parse. */ public static function parseMessage(string $message): array { - if (!$message) { - throw new \InvalidArgumentException('Invalid message'); - } - - $message = ltrim($message, "\r\n"); - - $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); - - if ($messageParts === false || count($messageParts) !== 2) { - throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); - } - - [$rawHeaders, $body] = $messageParts; - $rawHeaders .= "\r\n"; // Put back the delimiter we split previously - $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); - - if ($headerParts === false || count($headerParts) !== 2) { - throw new \InvalidArgumentException('Invalid message: Missing status line'); - } - - [$startLine, $rawHeaders] = $headerParts; - - if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { - // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 - $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); - } - - /** @var array[] $headerLines */ - $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); - - // If these aren't the same, then one line didn't match and there's an invalid header. - if ($count !== substr_count($rawHeaders, "\n")) { - // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 - if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { - throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); - } - - throw new \InvalidArgumentException('Invalid header syntax'); - } - - $headers = []; - - foreach ($headerLines as $headerLine) { - $headers[$headerLine[1]][] = $headerLine[2]; - } - - return [ - 'start-line' => $startLine, - 'headers' => $headers, - 'body' => $body, - ]; + return MessageParser::parseMessage($message); } /** * Constructs a URI for an HTTP request message. * + * The URI is composed from the start-line path and the `Host` header, using + * `https` when the host's port is `443` and `http` otherwise. Without a + * `Host` header, only the path is returned, with extra leading slashes + * collapsed so an origin-form target cannot be parsed as a network-path + * reference with its own authority. An `InvalidArgumentException` is thrown + * when the `Host` header is invalid. + * * @param string $path Path from the start-line * @param array $headers Array of headers (each value an array). */ public static function parseRequestUri(string $path, array $headers): string { - $hostKey = array_filter(array_keys($headers), function ($k) { - // Numeric array keys are converted to int by PHP. - $k = (string) $k; - - return strtolower($k) === 'host'; - }); - - // If no host is found, then a full URI cannot be constructed. - if (!$hostKey) { - return $path; - } - - $host = $headers[reset($hostKey)][0]; - $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; - - return $scheme . '://' . $host . '/' . ltrim($path, '/'); + return MessageParser::parseRequestUri($path, $headers); } /** * Parses a request message string into a request object. * + * The request-target must be in origin form, absolute form (without a + * userinfo component), authority form (`CONNECT`), or asterisk form + * (`OPTIONS`), and any `Host` header must be a single valid value; + * otherwise an `InvalidArgumentException` is thrown. Non-origin-form + * targets are preserved on the returned request via `withRequestTarget()`. + * * @param string $message Request message string. */ public static function parseRequest(string $message): RequestInterface { - $data = self::parseMessage($message); - $matches = []; - if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { - throw new \InvalidArgumentException('Invalid request string'); - } - $parts = explode(' ', $data['start-line'], 3); - $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; - - $request = new Request( - $parts[0], - $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], - $data['headers'], - $data['body'], - $version - ); - - return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + return MessageParser::parseRequest($message); } /** @@ -226,21 +251,6 @@ final class Message */ public static function parseResponse(string $message): ResponseInterface { - $data = self::parseMessage($message); - // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space - // between status-code and reason-phrase is required. But browsers accept - // responses without space and reason as well. - if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { - throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); - } - $parts = explode(' ', $data['start-line'], 3); - - return new Response( - (int) $parts[1], - $data['headers'], - $data['body'], - explode('/', $parts[0])[1], - $parts[2] ?? null - ); + return MessageParser::parseResponse($message); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageParser.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageParser.php new file mode 100644 index 000000000..22fe01899 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageParser.php @@ -0,0 +1,363 @@ +> $headerLines */ + if ($count === false) { + throw new \RuntimeException('Unable to parse HTTP headers: '.preg_last_error_msg()); + } + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc9112#section-5.2 + $hasFoldedHeader = preg_match(Rfc9112::HEADER_FOLD_REGEX, $rawHeaders); + + if ($hasFoldedHeader === false) { + throw new \RuntimeException('Unable to inspect HTTP header folding: '.preg_last_error_msg()); + } + + if ($hasFoldedHeader === 1) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = []; + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return [ + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ]; + } + + public static function parseRequestUri(string $path, array $headers): string + { + $host = self::getHostFromHeaders($headers); + + // If no host is found, then a full URI cannot be constructed. + // Collapse leading slashes so an origin-form target cannot be + // parsed as a network-path reference with its own authority. + if ($host === null) { + return self::normalizePathForOriginForm($path); + } + + [$authorityHost, $port] = self::parseHostHeaderAuthority($host); + $scheme = $port === 443 ? 'https' : 'http'; + + return $scheme.'://'.self::composeAuthority($authorityHost, $port).'/'.ltrim($path, '/'); + } + + private static function normalizePathForOriginForm(string $path): string + { + if (str_starts_with($path, '//')) { + return '/'.ltrim($path, '/'); + } + + return $path; + } + + /** + * @return array{0: string, 1: int|null} + */ + private static function parseHostHeaderAuthority(string $authority): array + { + $parsed = Rfc9112::parseHostHeader($authority); + if ($parsed === null) { + throw new \InvalidArgumentException('Invalid request string'); + } + + return $parsed; + } + + private static function composeAuthority(string $host, ?int $port): string + { + return $host.($port !== null ? ':'.$port : ''); + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function getHostFromHeaders(array $headers): ?string + { + $host = self::getSingleHostHeader($headers); + if ($host === null) { + return null; + } + + self::parseHostHeaderAuthority($host); + + return $host; + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function getSingleHostHeader(array $headers): ?string + { + $host = null; + $found = false; + + foreach ($headers as $name => $values) { + if (Utils::asciiToLower((string) $name) !== 'host') { + continue; + } + + if ($found || !is_array($values) || count($values) !== 1) { + throw new \InvalidArgumentException('Invalid request string'); + } + + $found = true; + $host = reset($values); + } + + if (!$found) { + return null; + } + + if (!is_string($host)) { + throw new \InvalidArgumentException('Invalid request string'); + } + + return $host; + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function parseRequestAuthorityUri(array $headers): string + { + $host = self::getHostFromHeaders($headers); + if ($host === null) { + return ''; + } + + [$authorityHost, $port] = self::parseHostHeaderAuthority($host); + $scheme = $port === 443 ? 'https' : 'http'; + + return $scheme.'://'.self::composeAuthority($authorityHost, $port); + } + + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + $matched = preg_match( + '/^(?P'.Rfc9110::TOKEN_PATTERN.') (?P'.Rfc9112::REQUEST_TARGET_PATTERN.') HTTP\/(?P'.Rfc9112::PROTOCOL_VERSION_PATTERN.')$/D', + $data['start-line'], + $matches + ); + + if ($matched === false) { + throw new \RuntimeException('Unable to parse request start line: '.preg_last_error_msg()); + } + + if ($matched === 0) { + throw new \InvalidArgumentException('Invalid request string'); + } + + self::getHostFromHeaders($data['headers']); + + if (str_starts_with($matches['target'], '/')) { + return new Request( + $matches['method'], + self::parseRequestUri($matches['target'], $data['headers']), + $data['headers'], + $data['body'], + $matches['version'] + ); + } + + $absoluteFormUri = self::parseAbsoluteFormRequestTarget($matches['target']); + if ($absoluteFormUri !== null) { + return (new Request( + $matches['method'], + $absoluteFormUri, + $data['headers'], + $data['body'], + $matches['version'] + ))->withRequestTarget($matches['target']); + } + + if (Rfc9112::isAsteriskFormRequestTarget($matches['method'], $matches['target'])) { + return (new Request( + $matches['method'], + self::parseRequestAuthorityUri($data['headers']), + $data['headers'], + $data['body'], + $matches['version'] + ))->withRequestTarget($matches['target']); + } + + $connectUri = self::parseConnectAuthorityFormRequestTarget($matches['method'], $matches['target']); + if ($connectUri !== null) { + return (new Request( + $matches['method'], + $connectUri, + $data['headers'], + $data['body'], + $matches['version'] + ))->withRequestTarget($matches['target']); + } + + throw new \InvalidArgumentException('Invalid request string'); + } + + private static function parseAbsoluteFormRequestTarget(string $target): ?Uri + { + if (!Rfc9112::isAbsoluteFormRequestTarget($target)) { + return null; + } + + $authority = substr($target, strpos($target, '//') + 2); + $authority = substr($authority, 0, strcspn($authority, '/?#')); + + // RFC 9110 deprecates userinfo in message target URIs and directs + // recipients to treat its presence as an error, since it can obscure + // the authority. Host headers and CONNECT targets already reject it. + if (str_contains($authority, '@')) { + return null; + } + + try { + $uri = new Uri($target); + } catch (\InvalidArgumentException $e) { + return null; + } + + if ($uri->getHost() === '') { + return null; + } + + try { + self::parseHostHeaderAuthority(self::composeAuthority($uri->getHost(), $uri->getPort())); + } catch (\InvalidArgumentException $e) { + return null; + } + + return $uri; + } + + private static function parseConnectAuthorityFormRequestTarget(string $method, string $target): ?Uri + { + if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) { + return null; + } + + $parsed = Rfc9112::parseHostHeader($target); + if ($parsed === null) { + return null; + } + + [$host, $port] = $parsed; + if ($port === null) { + return null; + } + + try { + return new Uri('//'.self::composeAuthority($host, $port)); + } catch (\InvalidArgumentException $e) { + return null; + } + } + + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://datatracker.ietf.org/doc/html/rfc9112#section-4 + // the space between status-code and reason-phrase is required. But + // browsers accept responses without space and reason as well. + $matched = preg_match( + '/^HTTP\/(?P'.Rfc9112::PROTOCOL_VERSION_PATTERN.') (?P[1-5][0-9]{2})(?: (?P'.Rfc9110::FIELD_VALUE_PATTERN.'))?$/D', + $data['start-line'], + $matches + ); + + if ($matched === false) { + throw new \RuntimeException('Unable to parse response start line: '.preg_last_error_msg()); + } + + if ($matched === 0) { + throw new \InvalidArgumentException(\sprintf('Invalid response string: %s', DiagnosticValue::escape($data['start-line']))); + } + + return new Response( + (int) $matches['status'], + $data['headers'], + $data['body'], + $matches['version'], + $matches['reason'] ?? null + ); + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php index 464bdfaa4..15c423238 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -12,31 +12,35 @@ use Psr\Http\Message\StreamInterface; */ trait MessageTrait { - /** @var array Map of all registered headers, as original name => array of values */ - private $headers = []; + /** @var string[][] Map of all registered headers, as original name => array of values */ + private array $headers = []; - /** @var array Map of lowercase header name => original name at registration */ - private $headerNames = []; + /** @var string[] Map of lowercase header name => original name at registration */ + private array $headerNames = []; - /** @var string */ - private $protocol = '1.1'; + private string $protocol = '1.1'; - /** @var StreamInterface|null */ - private $stream; + private ?StreamInterface $stream = null; public function getProtocolVersion(): string { return $this->protocol; } - public function withProtocolVersion($version): MessageInterface + /** + * @return static + */ + public function withProtocolVersion(string $version): MessageInterface { + $this->assertProtocolVersion($version); + if ($this->protocol === $version) { return $this; } $new = clone $this; $new->protocol = $version; + return $new; } @@ -45,14 +49,14 @@ trait MessageTrait return $this->headers; } - public function hasHeader($header): bool + public function hasHeader(string $name): bool { - return isset($this->headerNames[strtolower($header)]); + return isset($this->headerNames[Utils::asciiToLower($name)]); } - public function getHeader($header): array + public function getHeader(string $name): array { - $header = strtolower($header); + $header = Utils::asciiToLower($name); if (!isset($this->headerNames[$header])) { return []; @@ -63,57 +67,66 @@ trait MessageTrait return $this->headers[$header]; } - public function getHeaderLine($header): string + public function getHeaderLine(string $name): string { - return implode(', ', $this->getHeader($header)); + return implode(', ', $this->getHeader($name)); } - public function withHeader($header, $value): MessageInterface + /** + * @return static + */ + public function withHeader(string $name, $value): MessageInterface { - $this->assertHeader($header); - $value = $this->normalizeHeaderValue($value); - $normalized = strtolower($header); + $this->assertHeader($name); + $value = $this->normalizeHeaderValue($name, $value); + $normalized = Utils::asciiToLower($name); $new = clone $this; if (isset($new->headerNames[$normalized])) { unset($new->headers[$new->headerNames[$normalized]]); } - $new->headerNames[$normalized] = $header; - $new->headers[$header] = $value; + $new->headerNames[$normalized] = $name; + $new->headers[$name] = $value; return $new; } - public function withAddedHeader($header, $value): MessageInterface + /** + * @return static + */ + public function withAddedHeader(string $name, $value): MessageInterface { - $this->assertHeader($header); - $value = $this->normalizeHeaderValue($value); - $normalized = strtolower($header); + $this->assertHeader($name); + $value = $this->normalizeHeaderValue($name, $value); + $normalized = Utils::asciiToLower($name); $new = clone $this; if (isset($new->headerNames[$normalized])) { - $header = $this->headerNames[$normalized]; - $new->headers[$header] = array_merge($this->headers[$header], $value); + $name = $this->headerNames[$normalized]; + $new->headers[$name] = array_merge($this->headers[$name], $value); } else { - $new->headerNames[$normalized] = $header; - $new->headers[$header] = $value; + $new->headerNames[$normalized] = $name; + $new->headers[$name] = $value; } return $new; } - public function withoutHeader($header): MessageInterface + /** + * @return static + */ + public function withoutHeader(string $name): MessageInterface { - $normalized = strtolower($header); + $normalized = Utils::asciiToLower($name); if (!isset($this->headerNames[$normalized])) { return $this; } - $header = $this->headerNames[$normalized]; + $name = $this->headerNames[$normalized]; $new = clone $this; - unset($new->headers[$header], $new->headerNames[$normalized]); + unset($new->headers[$name], $new->headerNames[$normalized]); return $new; } @@ -127,6 +140,9 @@ trait MessageTrait return $this->stream; } + /** + * @return static + */ public function withBody(StreamInterface $body): MessageInterface { if ($body === $this->stream) { @@ -135,11 +151,12 @@ trait MessageTrait $new = clone $this; $new->stream = $body; + return $new; } /** - * @param array $headers + * @param (string|string[])[] $headers */ private function setHeaders(array $headers): void { @@ -149,8 +166,8 @@ trait MessageTrait $header = (string) $header; $this->assertHeader($header); - $value = $this->normalizeHeaderValue($value); - $normalized = strtolower($header); + $value = $this->normalizeHeaderValue($header, $value); + $normalized = Utils::asciiToLower($header); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; $this->headers[$header] = array_merge($this->headers[$header], $value); @@ -166,17 +183,17 @@ trait MessageTrait * * @return string[] */ - private function normalizeHeaderValue($value): array + private function normalizeHeaderValue(string $header, $value): array { + if (is_array($value) && $value === []) { + throw new \InvalidArgumentException('Header value must be a non-empty array or string.'); + } + if (!is_array($value)) { - return $this->trimAndValidateHeaderValues([$value]); + return $this->trimAndValidateHeaderValues($header, [$value]); } - if (count($value) === 0) { - throw new \InvalidArgumentException('Header value can not be an empty array.'); - } - - return $this->trimAndValidateHeaderValues($value); + return $this->trimAndValidateHeaderValues($header, $value); } /** @@ -191,48 +208,44 @@ trait MessageTrait * * @return string[] Trimmed header values * - * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + * @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5 */ - private function trimAndValidateHeaderValues(array $values): array + private function trimAndValidateHeaderValues(string $header, array $values): array { - return array_map(function ($value) { - if (!is_scalar($value) && null !== $value) { + return array_map(function ($value) use ($header): string { + if (!is_string($value)) { throw new \InvalidArgumentException(sprintf( - 'Header value must be scalar or null but %s provided.', - is_object($value) ? get_class($value) : gettype($value) + 'Header value must be a string or array of strings but %s provided.', + \get_debug_type($value) )); } - $trimmed = trim((string) $value, " \t"); - $this->assertValue($trimmed); + $trimmed = trim($value, " \t"); + $this->assertValue($header, $trimmed); return $trimmed; }, array_values($values)); } /** - * @see https://tools.ietf.org/html/rfc7230#section-3.2 - * - * @param mixed $header + * @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.1 */ - private function assertHeader($header): void + private function assertHeader(string $header): void { - if (!is_string($header)) { - throw new \InvalidArgumentException(sprintf( - 'Header name must be a string but %s provided.', - is_object($header) ? get_class($header) : gettype($header) - )); + if (!Rfc9110::isToken($header)) { + throw new \InvalidArgumentException(sprintf('Invalid header name: %s', DiagnosticValue::escape($header))); } + } - if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { - throw new \InvalidArgumentException( - sprintf('"%s" is not valid header name.', $header) - ); + private function assertProtocolVersion(string $version): void + { + if (!Rfc9112::isValidProtocolVersion($version)) { + throw new \InvalidArgumentException('Protocol version must be a valid HTTP version number.'); } } /** - * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * @see https://datatracker.ietf.org/doc/html/rfc9110#section-5.5 * * field-value = *( field-content / obs-fold ) * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] @@ -241,23 +254,26 @@ trait MessageTrait * obs-text = %x80-FF * obs-fold = CRLF 1*( SP / HTAB ) */ - private function assertValue(string $value): void + private function assertValue(string $header, string $value): void { - // The regular expression intentionally does not support the obs-fold production, because as - // per RFC 7230#3.2.4: + // The regular expression intentionally does not support the obs-fold + // production, because as per RFC 9112#5.2: // - // A sender MUST NOT generate a message that includes - // line folding (i.e., that has any field-value that contains a match to - // the obs-fold rule) unless the message is intended for packaging - // within the message/http media type. + // A sender MUST NOT generate a message that includes line folding + // (i.e., that has any field-value that contains a match to the obs-fold + // rule) unless the message is intended for packaging within the + // message/http media type. // - // Clients must not send a request with line folding and a server sending folded headers is - // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting - // folding is not likely to break any legitimate use case. - if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { - throw new \InvalidArgumentException( - sprintf('"%s" is not valid header value.', $value) - ); + // Clients must not send a request with line folding and a server + // sending folded headers is likely very rare. Line folding is a fairly + // obscure feature of HTTP/1.1 and thus not accepting folding is not + // likely to break any legitimate use case. + if (!Rfc9110::isFieldValue($value)) { + $reason = strpbrk($value, "\r\n") !== false + ? 'must not contain CR or LF characters' + : 'contains an invalid control character'; + + throw new \InvalidArgumentException(sprintf('Header "%s" %s.', DiagnosticValue::escape($header), $reason)); } } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php index 0debbd18c..0d17cd2ae 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MimeType.php @@ -6,50 +6,61 @@ namespace GuzzleHttp\Psr7; final class MimeType { + private function __construct() + { + } + private const MIME_TYPES = [ + '123' => 'application/vnd.lotus-1-2-3', '1km' => 'application/vnd.1000minds.decision-model+xml', + '210' => 'model/step', '3dml' => 'text/vnd.in3d.3dml', '3ds' => 'image/x-3ds', '3g2' => 'video/3gpp2', - '3gp' => 'video/3gp', + '3gp' => 'video/3gpp', '3gpp' => 'video/3gpp', '3mf' => 'model/3mf', '7z' => 'application/x-7z-compressed', '7zip' => 'application/x-7z-compressed', - '123' => 'application/vnd.lotus-1-2-3', 'aab' => 'application/x-authorware-bin', - 'aac' => 'audio/x-acc', + 'aac' => 'audio/aac', 'aam' => 'application/x-authorware-map', 'aas' => 'application/x-authorware-seg', 'abw' => 'application/x-abiword', - 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac' => 'application/pkix-attr-cert', 'ac3' => 'audio/ac3', 'acc' => 'application/vnd.americandynamics.acc', 'ace' => 'application/x-ace-compressed', 'acu' => 'application/vnd.acucobol', 'acutc' => 'application/vnd.acucorp', 'adp' => 'audio/adpcm', + 'adts' => 'audio/aac', 'aep' => 'application/vnd.audiograph', 'afm' => 'application/x-font-type1', 'afp' => 'application/vnd.ibm.modcap', 'age' => 'application/vnd.age', 'ahead' => 'application/vnd.ahead.space', - 'ai' => 'application/pdf', + 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'air' => 'application/vnd.adobe.air-application-installer-package+zip', 'ait' => 'application/vnd.dvb.ait', 'ami' => 'application/vnd.amiga.ami', + 'aml' => 'application/automationml-aml+xml', + 'amlx' => 'application/automationml-amlx+zip', 'amr' => 'audio/amr', 'apk' => 'application/vnd.android.package-archive', 'apng' => 'image/apng', 'appcache' => 'text/cache-manifest', + 'appinstaller' => 'application/appinstaller', 'application' => 'application/x-ms-application', + 'appx' => 'application/appx', + 'appxbundle' => 'application/appxbundle', 'apr' => 'application/vnd.lotus-approach', 'arc' => 'application/x-freearc', 'arj' => 'application/x-arj', - 'asc' => 'application/pgp-signature', + 'asc' => 'application/pgp-keys', 'asf' => 'video/x-ms-asf', 'asm' => 'text/x-asm', 'aso' => 'application/vnd.accpac.simply.aso', @@ -60,7 +71,7 @@ final class MimeType 'atomdeleted' => 'application/atomdeleted+xml', 'atomsvc' => 'application/atomsvc+xml', 'atx' => 'application/vnd.antix.game-component', - 'au' => 'audio/x-au', + 'au' => 'audio/basic', 'avci' => 'image/avci', 'avcs' => 'image/avcs', 'avi' => 'video/x-msvideo', @@ -71,15 +82,18 @@ final class MimeType 'azv' => 'image/vnd.airzip.accelerator.azv', 'azw' => 'application/vnd.amazon.ebook', 'b16' => 'image/vnd.pco.b16', + 'bary' => 'model/vnd.bary', 'bat' => 'application/x-msdownload', 'bcpio' => 'application/x-bcpio', 'bdf' => 'application/x-font-bdf', 'bdm' => 'application/vnd.syncml.dm+wbxml', - 'bdoc' => 'application/x-bdoc', + 'bdo' => 'application/vnd.nato.bindingdataobject+xml', + 'bdoc' => 'application/bdoc', 'bed' => 'application/vnd.realvnc.bed', 'bh2' => 'application/vnd.fujitsu.oasysprs', 'bin' => 'application/octet-stream', 'blb' => 'application/x-blorb', + 'blend' => 'application/x-blender', 'blorb' => 'application/x-blorb', 'bmi' => 'application/vnd.bmi', 'bmml' => 'application/vnd.balsamiq.bmml+xml', @@ -89,19 +103,22 @@ final class MimeType 'boz' => 'application/x-bzip2', 'bpk' => 'application/octet-stream', 'bpmn' => 'application/octet-stream', + 'brush' => 'application/vnd.procreate.brush', + 'brushset' => 'application/vnd.procreate.brushset', 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btf' => 'image/prs.btif', 'btif' => 'image/prs.btif', 'buffer' => 'application/octet-stream', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip2', 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', 'c4d' => 'application/vnd.clonk.c4group', 'c4f' => 'application/vnd.clonk.c4group', 'c4g' => 'application/vnd.clonk.c4group', 'c4p' => 'application/vnd.clonk.c4group', 'c4u' => 'application/vnd.clonk.c4group', - 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', - 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', 'cab' => 'application/vnd.ms-cab-compressed', 'caf' => 'audio/x-caf', 'cap' => 'application/vnd.tcpdump.pcap', @@ -125,7 +142,6 @@ final class MimeType 'cdmid' => 'application/cdmi-domain', 'cdmio' => 'application/cdmi-object', 'cdmiq' => 'application/cdmi-queue', - 'cdr' => 'application/cdr', 'cdx' => 'chemical/x-cdx', 'cdxml' => 'application/vnd.chemdraw+xml', 'cdy' => 'application/vnd.cinderella', @@ -140,7 +156,8 @@ final class MimeType 'cil' => 'application/vnd.ms-artgalry', 'cjs' => 'application/node', 'cla' => 'application/vnd.claymore', - 'class' => 'application/octet-stream', + 'class' => 'application/java-vm', + 'cld' => 'model/vnd.cld', 'clkk' => 'application/vnd.crick.clicker.keyboard', 'clkp' => 'application/vnd.crick.clicker.palette', 'clkt' => 'application/vnd.crick.clicker.template', @@ -175,6 +192,7 @@ final class MimeType 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'curl' => 'text/vnd.curl', + 'cwl' => 'application/cwl', 'cww' => 'application/prs.cww', 'cxt' => 'application/x-director', 'cxx' => 'text/x-c', @@ -185,6 +203,8 @@ final class MimeType 'davmount' => 'application/davmount+xml', 'dbf' => 'application/vnd.dbf', 'dbk' => 'application/docbook+xml', + 'dcm' => 'application/dicom', + 'dcmp' => 'application/vnd.dcmp+xml', 'dcr' => 'application/x-director', 'dcurl' => 'text/vnd.curl.dcurl', 'dd2' => 'application/vnd.oma.dd2+xml', @@ -197,6 +217,7 @@ final class MimeType 'der' => 'application/x-x509-ca-cert', 'dfac' => 'application/vnd.dreamfactory', 'dgc' => 'application/x-dgc-compressed', + 'dib' => 'image/bmp', 'dic' => 'text/x-c', 'dir' => 'application/x-director', 'dis' => 'application/vnd.mobius.dis', @@ -211,18 +232,22 @@ final class MimeType 'dmp' => 'application/vnd.tcpdump.pcap', 'dms' => 'application/octet-stream', 'dna' => 'application/vnd.dna', + 'dng' => 'image/x-adobe-dng', 'doc' => 'application/msword', - 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dot' => 'application/msword', - 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'dp' => 'application/vnd.osgi.dp', 'dpg' => 'application/vnd.dpgraph', + 'dpx' => 'image/dpx', 'dra' => 'audio/vnd.dra', 'drle' => 'image/dicom-rle', + 'drm' => 'application/vnd.procreate.dream', 'dsc' => 'text/prs.lines.tag', 'dssc' => 'application/dssc+der', + 'dst' => 'application/octet-stream', 'dtb' => 'application/x-dtbook+xml', 'dtd' => 'application/xml-dtd', 'dts' => 'audio/vnd.dts', @@ -255,7 +280,6 @@ final class MimeType 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', - 'es' => 'application/ecmascript', 'es3' => 'application/vnd.eszigno3+xml', 'esa' => 'application/vnd.osgi.subsystem', 'esf' => 'application/vnd.epson.esf', @@ -275,10 +299,12 @@ final class MimeType 'f4v' => 'video/mp4', 'f77' => 'text/x-fortran', 'f90' => 'text/x-fortran', + 'facti' => 'image/vnd.blockfact.facti', 'fbs' => 'image/vnd.fastbidsheet', + 'fbx' => 'application/vnd.autodesk.fbx', 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', 'fcs' => 'application/vnd.isac.fcs', - 'fdf' => 'application/vnd.fdf', + 'fdf' => 'application/fdf', 'fdt' => 'application/fdt+xml', 'fe_launch' => 'application/vnd.denovo.fcselayout-link', 'fg5' => 'application/vnd.fujitsu.oasysgp', @@ -320,21 +346,25 @@ final class MimeType 'gca' => 'application/x-gca-compressed', 'gdl' => 'model/vnd.gdl', 'gdoc' => 'application/vnd.google-apps.document', + 'gdraw' => 'application/vnd.google-apps.drawing', 'ged' => 'text/vnd.familysearch.gedcom', 'geo' => 'application/vnd.dynageo', 'geojson' => 'application/geo+json', 'gex' => 'application/vnd.geometry-explorer', + 'gform' => 'application/vnd.google-apps.form', 'ggb' => 'application/vnd.geogebra.file', + 'ggs' => 'application/vnd.geogebra.slides', 'ggt' => 'application/vnd.geogebra.tool', 'ghf' => 'application/vnd.groove-help', 'gif' => 'image/gif', 'gim' => 'application/vnd.groove-identity-message', + 'gjam' => 'application/vnd.google-apps.jam', 'glb' => 'model/gltf-binary', 'gltf' => 'model/gltf+json', + 'gmap' => 'application/vnd.google-apps.map', 'gml' => 'application/gml+xml', 'gmx' => 'application/vnd.gmx', 'gnumeric' => 'application/x-gnumeric', - 'gpg' => 'application/gpg-keys', 'gph' => 'application/vnd.flographit', 'gpx' => 'application/gpx+xml', 'gqf' => 'application/vnd.grafeq', @@ -344,8 +374,10 @@ final class MimeType 'gre' => 'application/vnd.geometry-explorer', 'grv' => 'application/vnd.groove-injector', 'grxml' => 'application/srgs+xml', + 'gscript' => 'application/vnd.google-apps.script', 'gsf' => 'application/x-font-ghostscript', 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gsite' => 'application/vnd.google-apps.site', 'gslides' => 'application/vnd.google-apps.presentation', 'gtar' => 'application/x-gtar', 'gtm' => 'application/vnd.groove-tool-message', @@ -377,7 +409,6 @@ final class MimeType 'hpid' => 'application/vnd.hp-hpid', 'hps' => 'application/vnd.hp-hps', 'hqx' => 'application/mac-binhex40', - 'hsj2' => 'image/hsj2', 'htc' => 'text/x-component', 'htke' => 'application/vnd.kenameaapp', 'htm' => 'text/html', @@ -389,7 +420,7 @@ final class MimeType 'icc' => 'application/vnd.iccprofile', 'ice' => 'x-conference/x-cooltalk', 'icm' => 'application/vnd.iccprofile', - 'ico' => 'image/x-icon', + 'ico' => 'image/vnd.microsoft.icon', 'ics' => 'text/calendar', 'ief' => 'image/ief', 'ifb' => 'text/calendar', @@ -404,6 +435,7 @@ final class MimeType 'imp' => 'application/vnd.accpac.simply.imp', 'ims' => 'application/vnd.ms-ims', 'in' => 'text/plain', + 'indd' => 'application/x-indesign', 'ini' => 'text/plain', 'ink' => 'application/inkml+xml', 'inkml' => 'application/inkml+xml', @@ -411,6 +443,7 @@ final class MimeType 'iota' => 'application/vnd.astraea-software.iota', 'ipfix' => 'application/ipfix', 'ipk' => 'application/vnd.shana.informed.package', + 'ipynb' => 'application/x-ipynb+json', 'irm' => 'application/vnd.ibm.rights-management', 'irp' => 'application/vnd.irepository.package+xml', 'iso' => 'application/x-iso9660-image', @@ -420,10 +453,13 @@ final class MimeType 'ivu' => 'application/vnd.immervision-ivu', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'jade' => 'text/jade', + 'jaii' => 'image/jaii', + 'jais' => 'image/jais', 'jam' => 'application/vnd.jam', 'jar' => 'application/java-archive', 'jardiff' => 'application/x-java-archive-diff', 'java' => 'text/x-java-source', + 'jfif' => 'image/jpeg', 'jhc' => 'image/jphc', 'jisp' => 'application/vnd.jisp', 'jls' => 'image/jls', @@ -437,17 +473,19 @@ final class MimeType 'jpf' => 'image/jpx', 'jpg' => 'image/jpeg', 'jpg2' => 'image/jp2', - 'jpgm' => 'video/jpm', + 'jpgm' => 'image/jpm', 'jpgv' => 'video/jpeg', 'jph' => 'image/jph', - 'jpm' => 'video/jpm', + 'jpm' => 'image/jpm', 'jpx' => 'image/jpx', - 'js' => 'application/javascript', + 'js' => 'text/javascript', 'json' => 'application/json', 'json5' => 'application/json5', 'jsonld' => 'application/ld+json', 'jsonml' => 'application/jsonml+json', 'jsx' => 'text/jsx', + 'jt' => 'model/jt', + 'jxl' => 'image/jxl', 'jxr' => 'image/jxr', 'jxra' => 'image/jxra', 'jxrs' => 'image/jxrs', @@ -457,9 +495,10 @@ final class MimeType 'jxss' => 'image/jxss', 'kar' => 'audio/midi', 'karbon' => 'application/vnd.kde.karbon', + 'kbl' => 'application/kbl+xml', 'kdb' => 'application/octet-stream', 'kdbx' => 'application/x-keepass2', - 'key' => 'application/x-iwork-keynote-sffkey', + 'key' => 'application/vnd.apple.keynote', 'kfo' => 'application/vnd.kde.kformula', 'kia' => 'application/vnd.kidspiration', 'kml' => 'application/vnd.google-earth.kml+xml', @@ -484,7 +523,7 @@ final class MimeType 'les' => 'application/vnd.hhe.lesson-player', 'less' => 'text/less', 'lgr' => 'application/lgr+xml', - 'lha' => 'application/octet-stream', + 'lha' => 'application/x-lzh-compressed', 'link66' => 'application/vnd.route66.link66+xml', 'list' => 'text/plain', 'list3820' => 'application/vnd.ibm.modcap', @@ -493,6 +532,7 @@ final class MimeType 'lnk' => 'application/x-ms-shortcut', 'log' => 'text/plain', 'lostxml' => 'application/lost+xml', + 'lottie' => 'application/zip+dotlottie', 'lrf' => 'application/octet-stream', 'lrm' => 'application/vnd.ms-lrm', 'ltf' => 'application/vnd.frogans.ltf', @@ -500,21 +540,24 @@ final class MimeType 'luac' => 'application/x-lua-bytecode', 'lvp' => 'audio/vnd.lucent.voice', 'lwp' => 'application/vnd.lotus-wordpro', - 'lzh' => 'application/octet-stream', - 'm1v' => 'video/mpeg', - 'm2a' => 'audio/mpeg', - 'm2v' => 'video/mpeg', - 'm3a' => 'audio/mpeg', - 'm3u' => 'text/plain', - 'm3u8' => 'application/vnd.apple.mpegurl', - 'm4a' => 'audio/x-m4a', - 'm4p' => 'application/mp4', - 'm4s' => 'video/iso.segment', - 'm4u' => 'application/vnd.mpegurl', - 'm4v' => 'video/x-m4v', + 'lzh' => 'application/x-lzh-compressed', 'm13' => 'application/x-msmediaview', 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2t' => 'video/mp2t', + 'm2ts' => 'video/mp2t', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4b' => 'audio/mp4', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', 'ma' => 'application/mathematica', 'mads' => 'application/mads+xml', 'maei' => 'application/mmt-aei+xml', @@ -545,6 +588,8 @@ final class MimeType 'mft' => 'application/rpki-manifest', 'mgp' => 'application/vnd.osgeo.mapguide.package', 'mgz' => 'application/vnd.proteus.magazine', + 'mht' => 'message/rfc822', + 'mhtml' => 'message/rfc822', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mie' => 'application/x-mie', @@ -552,12 +597,12 @@ final class MimeType 'mime' => 'message/rfc822', 'mj2' => 'video/mj2', 'mjp2' => 'video/mj2', - 'mjs' => 'application/javascript', - 'mk3d' => 'video/x-matroska', - 'mka' => 'audio/x-matroska', + 'mjs' => 'text/javascript', + 'mk3d' => 'video/matroska-3d', + 'mka' => 'audio/matroska', 'mkd' => 'text/x-markdown', 'mks' => 'video/x-matroska', - 'mkv' => 'video/x-matroska', + 'mkv' => 'video/matroska', 'mlp' => 'application/vnd.dolby.mlp', 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', 'mmf' => 'application/vnd.smaf', @@ -570,13 +615,13 @@ final class MimeType 'mov' => 'video/quicktime', 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', 'mp2a' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4s' => 'application/mp4', 'mp4v' => 'video/mp4', - 'mp21' => 'application/mp21', 'mpc' => 'application/vnd.mophun.certificate', 'mpd' => 'application/dash+xml', 'mpe' => 'video/mpeg', @@ -601,13 +646,15 @@ final class MimeType 'msf' => 'application/vnd.epson.msf', 'msg' => 'application/vnd.ms-outlook', 'msh' => 'model/mesh', - 'msi' => 'application/x-msdownload', + 'msi' => 'application/octet-stream', + 'msix' => 'application/msix', + 'msixbundle' => 'application/msixbundle', 'msl' => 'application/vnd.mobius.msl', 'msm' => 'application/octet-stream', 'msp' => 'application/octet-stream', 'msty' => 'application/vnd.muvee.style', 'mtl' => 'model/mtl', - 'mts' => 'model/vnd.mts', + 'mts' => 'video/mp2t', 'mus' => 'application/vnd.musician', 'musd' => 'application/mmt-usd+xml', 'musicxml' => 'application/vnd.recordare.musicxml+xml', @@ -626,6 +673,7 @@ final class MimeType 'nbp' => 'application/vnd.wolfram.player', 'nc' => 'application/x-netcdf', 'ncx' => 'application/x-dtbncx+xml', + 'ndjson' => 'application/x-ndjson', 'nfo' => 'text/x-nfo', 'ngdat' => 'application/vnd.nokia.n-gage.data', 'nitf' => 'application/vnd.nitf', @@ -640,7 +688,7 @@ final class MimeType 'nsf' => 'application/vnd.lotus-notes', 'nt' => 'application/n-triples', 'ntf' => 'application/vnd.nitf', - 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'numbers' => 'application/vnd.apple.numbers', 'nzb' => 'application/x-nzb', 'oa2' => 'application/vnd.fujitsu.oasys2', 'oa3' => 'application/vnd.fujitsu.oasys3', @@ -665,6 +713,8 @@ final class MimeType 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'omdoc' => 'application/omdoc+xml', + 'one' => 'application/onenote', + 'onea' => 'application/onenote', 'onepkg' => 'application/onenote', 'onetmp' => 'application/onenote', 'onetoc' => 'application/onenote', @@ -673,7 +723,7 @@ final class MimeType 'opml' => 'text/x-opml', 'oprc' => 'application/vnd.palm', 'opus' => 'audio/ogg', - 'org' => 'text/x-org', + 'org' => 'application/vnd.lotus-organizer', 'osf' => 'application/vnd.yamaha.openscoreformat', 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'osm' => 'application/vnd.openstreetmap.data+xml', @@ -691,17 +741,20 @@ final class MimeType 'oxps' => 'application/oxps', 'oxt' => 'application/vnd.openofficeorg.extension', 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p21' => 'model/step', 'p7a' => 'application/x-pkcs7-signature', 'p7b' => 'application/x-pkcs7-certificates', 'p7c' => 'application/pkcs7-mime', + 'p7e' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7r' => 'application/x-pkcs7-certreqresp', 'p7s' => 'application/pkcs7-signature', 'p8' => 'application/pkcs8', - 'p10' => 'application/x-pkcs10', - 'p12' => 'application/x-pkcs12', 'pac' => 'application/x-ns-proxy-autoconfig', - 'pages' => 'application/x-iwork-pages-sffpages', + 'pages' => 'application/vnd.apple.pages', + 'parquet' => 'application/vnd.apache.parquet', 'pas' => 'text/x-pascal', 'paw' => 'application/vnd.pawaafile', 'pbd' => 'application/vnd.powerbuilder6', @@ -712,8 +765,8 @@ final class MimeType 'pclxl' => 'application/vnd.hp-pclxl', 'pct' => 'image/x-pict', 'pcurl' => 'application/vnd.curl.pcurl', - 'pcx' => 'image/x-pcx', - 'pdb' => 'application/x-pilot', + 'pcx' => 'image/vnd.zbrush.pcx', + 'pdb' => 'application/vnd.palm', 'pde' => 'text/x-processing', 'pdf' => 'application/pdf', 'pem' => 'application/x-x509-user-cert', @@ -724,7 +777,7 @@ final class MimeType 'pfx' => 'application/x-pkcs12', 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', - 'pgp' => 'application/pgp', + 'pgp' => 'application/pgp-encrypted', 'phar' => 'application/octet-stream', 'php' => 'application/x-httpd-php', 'php3' => 'application/x-httpd-php', @@ -747,17 +800,17 @@ final class MimeType 'pnm' => 'image/x-portable-anymap', 'portpkg' => 'application/vnd.macports.portpkg', 'pot' => 'application/vnd.ms-powerpoint', - 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppa' => 'application/vnd.ms-powerpoint', - 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', 'ppd' => 'application/vnd.cups-ppd', 'ppm' => 'image/x-portable-pixmap', 'pps' => 'application/vnd.ms-powerpoint', - 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'ppt' => 'application/powerpoint', - 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pqa' => 'application/vnd.palm', 'prc' => 'model/prc', @@ -766,15 +819,19 @@ final class MimeType 'provx' => 'application/provenance+xml', 'ps' => 'application/postscript', 'psb' => 'application/vnd.3gpp.pic-bw-small', - 'psd' => 'application/x-photoshop', + 'psd' => 'image/vnd.adobe.photoshop', 'psf' => 'application/x-font-linux-psf', 'pskcxml' => 'application/pskc+xml', 'pti' => 'image/prs.pti', 'ptid' => 'application/vnd.pvi.ptid1', 'pub' => 'application/x-mspublisher', + 'pv' => 'application/octet-stream', 'pvb' => 'application/vnd.3gpp.pic-bw-var', 'pwn' => 'application/vnd.3m.post-it-notes', + 'pxf' => 'application/octet-stream', 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyo' => 'model/vnd.pytha.pyox', + 'pyox' => 'model/vnd.pytha.pyox', 'pyv' => 'video/vnd.ms-playready.media.pyv', 'qam' => 'application/vnd.epson.quickanime', 'qbo' => 'application/vnd.intu.qbo', @@ -791,7 +848,7 @@ final class MimeType 'ram' => 'audio/x-pn-realaudio', 'raml' => 'application/raml+yaml', 'rapd' => 'application/route-apd+xml', - 'rar' => 'application/x-rar', + 'rar' => 'application/vnd.rar', 'ras' => 'image/x-cmu-raster', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', 'rdf' => 'application/rdf+xml', @@ -806,7 +863,7 @@ final class MimeType 'rl' => 'application/resource-lists+xml', 'rlc' => 'image/vnd.fujixerox.edmics-rlc', 'rld' => 'application/resource-lists-diff+xml', - 'rm' => 'audio/x-pn-realaudio', + 'rm' => 'application/vnd.rn-realmedia', 'rmi' => 'audio/midi', 'rmp' => 'audio/x-pn-realaudio-plugin', 'rms' => 'application/vnd.jcp.javame.midlet-rms', @@ -816,7 +873,7 @@ final class MimeType 'roa' => 'application/rpki-roa', 'roff' => 'text/troff', 'rp9' => 'application/vnd.cloanto.rp9', - 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpm' => 'application/x-redhat-package-manager', 'rpss' => 'application/vnd.nokia.radio-presets', 'rpst' => 'application/vnd.nokia.radio-preset', 'rq' => 'application/sparql-query', @@ -850,7 +907,7 @@ final class MimeType 'sdkm' => 'application/vnd.solent.sdkm+xml', 'sdp' => 'application/sdp', 'sdw' => 'application/vnd.stardivision.writer', - 'sea' => 'application/octet-stream', + 'sea' => 'application/x-sea', 'see' => 'application/vnd.seemail', 'seed' => 'application/vnd.fdsn.seed', 'sema' => 'application/vnd.sema', @@ -895,8 +952,8 @@ final class MimeType 'slt' => 'application/vnd.epson.salt', 'sm' => 'application/vnd.stepmania.stepchart', 'smf' => 'application/vnd.stardivision.math', - 'smi' => 'application/smil', - 'smil' => 'application/smil', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', 'smv' => 'video/x-smv', 'smzip' => 'application/vnd.stepmania.package', 'snd' => 'audio/basic', @@ -910,7 +967,9 @@ final class MimeType 'spp' => 'application/scvp-vp-response', 'spq' => 'application/scvp-vp-request', 'spx' => 'audio/ogg', - 'sql' => 'application/x-sql', + 'sql' => 'application/sql', + 'sqlite' => 'application/vnd.sqlite3', + 'sqlite3' => 'application/vnd.sqlite3', 'src' => 'application/x-wais-source', 'srt' => 'application/x-subrip', 'sru' => 'application/sru+xml', @@ -923,10 +982,13 @@ final class MimeType 'st' => 'application/vnd.sailingtracker.track', 'stc' => 'application/vnd.sun.xml.calc.template', 'std' => 'application/vnd.sun.xml.draw.template', + 'step' => 'model/step', 'stf' => 'application/vnd.wt.stf', 'sti' => 'application/vnd.sun.xml.impress.template', 'stk' => 'application/hyperstudio', 'stl' => 'model/stl', + 'stp' => 'model/step', + 'stpnc' => 'model/step', 'stpx' => 'model/step+xml', 'stpxz' => 'model/step-xml+zip', 'stpz' => 'model/step+zip', @@ -934,7 +996,7 @@ final class MimeType 'stw' => 'application/vnd.sun.xml.writer.template', 'styl' => 'text/stylus', 'stylus' => 'text/stylus', - 'sub' => 'text/vnd.dvb.subtitle', + 'sub' => 'image/vnd.dvb.subtitle', 'sus' => 'application/vnd.sus-calendar', 'susp' => 'application/vnd.sus-calendar', 'sv4cpio' => 'application/x-sv4cpio', @@ -953,6 +1015,7 @@ final class MimeType 'sxi' => 'application/vnd.sun.xml.impress', 'sxm' => 'application/vnd.sun.xml.math', 'sxw' => 'application/vnd.sun.xml.writer', + 'systemverify' => 'application/vnd.pp.systemverify+xml', 't' => 'text/troff', 't3' => 'application/x-t3vm-image', 't38' => 'image/t38', @@ -974,7 +1037,7 @@ final class MimeType 'tfm' => 'application/x-tex-tfm', 'tfx' => 'image/tiff-fx', 'tga' => 'image/x-tga', - 'tgz' => 'application/x-tar', + 'tgz' => 'application/gzip', 'thmx' => 'application/vnd.ms-officetheme', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', @@ -1000,12 +1063,12 @@ final class MimeType 'txd' => 'application/vnd.genomatix.tuxedo', 'txf' => 'application/vnd.mobius.txf', 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', 'u3d' => 'model/u3d', 'u8dsn' => 'message/global-delivery-status', 'u8hdr' => 'message/global-headers', 'u8mdn' => 'message/global-disposition-notification', 'u8msg' => 'message/global', - 'u32' => 'application/x-authorware-bin', 'ubj' => 'application/ubjson', 'udeb' => 'application/x-debian-package', 'ufd' => 'application/vnd.ufdl', @@ -1013,10 +1076,12 @@ final class MimeType 'ulx' => 'application/x-glulx', 'umj' => 'application/vnd.umajin', 'unityweb' => 'application/vnd.unity', + 'uo' => 'application/vnd.uoml+xml', 'uoml' => 'application/vnd.uoml+xml', 'uri' => 'text/uri-list', 'uris' => 'text/uri-list', 'urls' => 'text/uri-list', + 'usda' => 'model/vnd.usda', 'usdz' => 'model/vnd.usdz+zip', 'ustar' => 'application/x-ustar', 'utz' => 'application/vnd.uiq.theme', @@ -1059,16 +1124,18 @@ final class MimeType 'vcx' => 'application/vnd.vcx', 'vdi' => 'application/x-virtualbox-vdi', 'vds' => 'model/vnd.sap.vds', + 'vdx' => 'application/vnd.ms-visio.viewer', + 'vec' => 'application/vec+xml', 'vhd' => 'application/x-virtualbox-vhd', 'vis' => 'application/vnd.visionary', 'viv' => 'video/vnd.vivo', - 'vlc' => 'application/videolan', 'vmdk' => 'application/x-virtualbox-vmdk', 'vob' => 'video/x-ms-vob', 'vor' => 'application/vnd.stardivision.writer', 'vox' => 'application/x-authorware-bin', 'vrml' => 'model/vrml', 'vsd' => 'application/vnd.visio', + 'vsdx' => 'application/vnd.visio', 'vsf' => 'application/vnd.vsf', 'vss' => 'application/vnd.visio', 'vst' => 'application/vnd.visio', @@ -1076,17 +1143,18 @@ final class MimeType 'vtf' => 'image/vnd.valve.source.texture', 'vtt' => 'text/vtt', 'vtu' => 'model/vnd.vtu', + 'vtx' => 'application/vnd.visio', 'vxml' => 'application/voicexml+xml', 'w3d' => 'application/x-director', 'wad' => 'application/x-doom', 'wadl' => 'application/vnd.sun.wadl+xml', 'war' => 'application/java-archive', 'wasm' => 'application/wasm', - 'wav' => 'audio/x-wav', + 'wav' => 'audio/wav', 'wax' => 'audio/x-ms-wax', 'wbmp' => 'image/vnd.wap.wbmp', 'wbs' => 'application/vnd.criticaltools.wbs+xml', - 'wbxml' => 'application/wbxml', + 'wbxml' => 'application/vnd.wap.wbxml', 'wcm' => 'application/vnd.ms-works', 'wdb' => 'application/vnd.ms-works', 'wdp' => 'image/vnd.ms-photo', @@ -1096,6 +1164,7 @@ final class MimeType 'webmanifest' => 'application/manifest+json', 'webp' => 'image/webp', 'wg' => 'application/vnd.pmi.widget', + 'wgsl' => 'text/wgsl', 'wgt' => 'application/widget', 'wif' => 'application/watcherinfo+xml', 'wks' => 'application/vnd.ms-works', @@ -1104,12 +1173,12 @@ final class MimeType 'wmd' => 'application/x-ms-wmd', 'wmf' => 'image/wmf', 'wml' => 'text/vnd.wap.wml', - 'wmlc' => 'application/wmlc', + 'wmlc' => 'application/vnd.wap.wmlc', 'wmls' => 'text/vnd.wap.wmlscript', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wmv' => 'video/x-ms-wmv', 'wmx' => 'video/x-ms-wmx', - 'wmz' => 'application/x-msmetafile', + 'wmz' => 'application/x-ms-wmz', 'woff' => 'font/woff', 'woff2' => 'font/woff2', 'word' => 'application/msword', @@ -1124,13 +1193,13 @@ final class MimeType 'wspolicy' => 'application/wspolicy+xml', 'wtb' => 'application/vnd.webturbo', 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', 'x3d' => 'model/x3d+xml', 'x3db' => 'model/x3d+fastinfoset', 'x3dbz' => 'model/x3d+binary', 'x3dv' => 'model/x3d-vrml', 'x3dvz' => 'model/x3d+vrml', 'x3dz' => 'model/x3d+xml', - 'x32' => 'application/x-authorware-bin', 'x_b' => 'model/vnd.parasolid.transmit.binary', 'x_t' => 'model/vnd.parasolid.transmit.text', 'xaml' => 'application/xaml+xml', @@ -1142,6 +1211,7 @@ final class MimeType 'xbm' => 'image/x-xbitmap', 'xca' => 'application/xcap-caps+xml', 'xcs' => 'application/calendar+xml', + 'xdcf' => 'application/vnd.gov.sk.xmldatacontainer+xml', 'xdf' => 'application/xcap-diff+xml', 'xdm' => 'application/vnd.syncml.dm+xml', 'xdp' => 'application/vnd.adobe.xdp+xml', @@ -1150,24 +1220,25 @@ final class MimeType 'xel' => 'application/xcap-el+xml', 'xenc' => 'application/xenc+xml', 'xer' => 'application/patch-ops-error+xml', - 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdf' => 'application/xfdf', 'xfdl' => 'application/vnd.xfdl', 'xht' => 'application/xhtml+xml', + 'xhtm' => 'application/vnd.pwg-xhtml-print+xml', 'xhtml' => 'application/xhtml+xml', 'xhvml' => 'application/xv+xml', 'xif' => 'image/vnd.xiff', - 'xl' => 'application/excel', + 'xl' => 'application/vnd.ms-excel', 'xla' => 'application/vnd.ms-excel', - 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', 'xlc' => 'application/vnd.ms-excel', 'xlf' => 'application/xliff+xml', 'xlm' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', - 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlt' => 'application/vnd.ms-excel', - 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlw' => 'application/vnd.ms-excel', 'xm' => 'audio/xm', @@ -1183,7 +1254,8 @@ final class MimeType 'xpw' => 'application/vnd.intercon.formnet', 'xpx' => 'application/vnd.intercon.formnet', 'xsd' => 'application/xml', - 'xsl' => 'application/xml', + 'xsf' => 'application/prs.xsf+xml', + 'xsl' => 'application/xslt+xml', 'xslt' => 'application/xslt+xml', 'xsm' => 'application/vnd.syncml+xml', 'xspf' => 'application/xspf+xml', @@ -1216,9 +1288,9 @@ final class MimeType ]; /** - * Determines the mimetype of a file by looking at its extension. + * Determines the MIME type of a file by looking at its extension. * - * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json */ public static function fromFilename(string $filename): ?string { @@ -1226,12 +1298,12 @@ final class MimeType } /** - * Maps a file extensions to a mimetype. + * Maps a file extension to a MIME type. * - * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json */ public static function fromExtension(string $extension): ?string { - return self::MIME_TYPES[strtolower($extension)] ?? null; + return self::MIME_TYPES[Utils::asciiToLower($extension)] ?? null; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php index 3e12b74d1..cbf66cad1 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -13,28 +13,40 @@ use Psr\Http\Message\StreamInterface; final class MultipartStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var string */ - private $boundary; + private string $boundary; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; + + private const BOUNDARY_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=? "; /** - * @param array $elements Array of associative arrays, each containing a - * required "name" key mapping to the form field, - * name, a required "contents" key mapping to a - * StreamInterface/resource/string, an optional - * "headers" associative array of custom headers, - * and an optional "filename" key mapping to a - * string to send as the filename in the part. - * @param string $boundary You can optionally provide a specific boundary + * @param array $elements Array of associative arrays, each containing a + * required "name" key mapping to the form field, + * name, a required "contents" key mapping to any + * non-array value accepted by Utils::streamFor() + * (non-string scalar field values are cast to + * string), or an array for nested expansion. + * Optional keys include "headers" (associative + * array of custom headers) and "filename" (string + * to send as the filename in the part). + * When "contents" is an array, it is recursively + * expanded into multiple fields using bracket notation + * (e.g., name[0][key]). Empty arrays produce no fields. + * The "filename" and "headers" options cannot be used + * with array contents. + * @param string|null $boundary You can optionally provide a specific boundary * * @throws \InvalidArgumentException */ - public function __construct(array $elements = [], string $boundary = null) + public function __construct(array $elements = [], ?string $boundary = null) { - $this->boundary = $boundary ?: bin2hex(random_bytes(20)); + if ($boundary !== null) { + self::validateBoundary($boundary); + } + + $this->boundary = $boundary ?? bin2hex(random_bytes(20)); $this->stream = $this->createStream($elements); } @@ -51,16 +63,21 @@ final class MultipartStream implements StreamInterface /** * Get the headers needed before transferring the content of a POST file * - * @param array $headers + * @param array $headers */ private function getHeaders(array $headers): string { $str = ''; foreach ($headers as $key => $value) { + $key = (string) $key; + + self::validatePartHeaderName($key); + self::validatePartHeaderValue($key, $value); + $str .= "{$key}: {$value}\r\n"; } - return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + return "--{$this->boundary}\r\n".rtrim($str, "\r\n")."\r\n\r\n"; } /** @@ -72,7 +89,7 @@ final class MultipartStream implements StreamInterface foreach ($elements as $element) { if (!is_array($element)) { - throw new \UnexpectedValueException("An array is expected"); + throw new \UnexpectedValueException('An array is expected'); } $this->addElement($stream, $element); } @@ -91,17 +108,45 @@ final class MultipartStream implements StreamInterface } } - $element['contents'] = Utils::streamFor($element['contents']); + if (!is_string($element['name']) && !is_int($element['name'])) { + throw new \InvalidArgumentException("The 'name' key must be a string or integer"); + } + + if (is_array($element['contents'])) { + if (array_key_exists('filename', $element) || array_key_exists('headers', $element)) { + throw new \InvalidArgumentException( + "The 'filename' and 'headers' options cannot be used when 'contents' is an array" + ); + } + + $this->addNestedElements($stream, $element['contents'], (string) $element['name']); + + return; + } + + $contents = $element['contents']; + if (is_scalar($contents) && !is_string($contents)) { + // Multipart field values are byte strings on the wire, so finite + // numeric and boolean field values are cast to string here rather + // than rejected by streamFor(). Non-finite floats cannot be + // represented and are rejected. + if (is_float($contents) && !is_finite($contents)) { + throw new \InvalidArgumentException('Cannot create a stream from a non-finite float.'); + } + + $contents = (string) $contents; + } + $element['contents'] = Utils::streamFor($contents); if (empty($element['filename'])) { $uri = $element['contents']->getMetadata('uri'); - if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + if ($uri && \is_string($uri) && !str_starts_with($uri, 'php://') && !str_starts_with($uri, 'data://')) { $element['filename'] = $uri; } } [$body, $headers] = $this->createElement( - $element['name'], + (string) $element['name'], $element['contents'], $element['filename'] ?? null, $element['headers'] ?? [] @@ -112,48 +157,130 @@ final class MultipartStream implements StreamInterface $stream->addStream(Utils::streamFor("\r\n")); } + /** + * Recursively expand array contents into multiple form fields. + * + * @param array $contents + */ + private function addNestedElements(AppendStream $stream, array $contents, string $root): void + { + foreach ($contents as $key => $value) { + $fieldName = $root === '' ? sprintf('[%s]', (string) $key) : sprintf('%s[%s]', $root, (string) $key); + + if (is_array($value)) { + $this->addNestedElements($stream, $value, $fieldName); + } else { + $this->addElement($stream, ['name' => $fieldName, 'contents' => $value]); + } + } + } + + /** + * @param array $headers + * + * @return array{0: StreamInterface, 1: array} + */ private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array { + $headers = self::normalizePartHeaders($headers); + // Set a default content-disposition header if one was no provided - $disposition = $this->getHeader($headers, 'content-disposition'); + $disposition = self::getHeader($headers, 'content-disposition'); if (!$disposition) { + $escapedName = self::escapeContentDispositionParameter($name); $headers['Content-Disposition'] = ($filename === '0' || $filename) ? sprintf( 'form-data; name="%s"; filename="%s"', - $name, - basename($filename) + $escapedName, + self::escapeContentDispositionParameter(basename($filename)) ) - : "form-data; name=\"{$name}\""; - } - - // Set a default content-length header if one was no provided - $length = $this->getHeader($headers, 'content-length'); - if (!$length) { - if ($length = $stream->getSize()) { - $headers['Content-Length'] = (string) $length; - } + : sprintf('form-data; name="%s"', $escapedName); } // Set a default Content-Type if one was not supplied - $type = $this->getHeader($headers, 'content-type'); + $type = self::getHeader($headers, 'content-type'); if (!$type && ($filename === '0' || $filename)) { - if ($type = MimeType::fromFilename($filename)) { - $headers['Content-Type'] = $type; - } + $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream'; } return [$stream, $headers]; } - private function getHeader(array $headers, string $key) + /** + * @param array $headers + */ + private static function getHeader(array $headers, string $key): ?string { - $lowercaseHeader = strtolower($key); + $lowercaseHeader = Utils::asciiToLower($key); foreach ($headers as $k => $v) { - if (strtolower($k) === $lowercaseHeader) { + if (Utils::asciiToLower((string) $k) === $lowercaseHeader) { return $v; } } return null; } + + private static function validateBoundary(string $boundary): void + { + $length = strlen($boundary); + + if ($length < 1 || $length > 70 || $boundary[$length - 1] === ' ') { + throw new \InvalidArgumentException('Invalid multipart boundary.'); + } + + if (strspn($boundary, self::BOUNDARY_CHARS) !== $length) { + throw new \InvalidArgumentException('Invalid multipart boundary.'); + } + } + + /** + * @param array $headers + * + * @return array + */ + private static function normalizePartHeaders(array $headers): array + { + $normalized = []; + + foreach ($headers as $key => $value) { + $key = (string) $key; + + self::validatePartHeaderName($key); + + if (!is_string($value)) { + throw new \InvalidArgumentException('Multipart part header value must be a string.'); + } + + self::validatePartHeaderValue($key, $value); + + $normalized[$key] = $value; + } + + return $normalized; + } + + private static function validatePartHeaderName(string $name): void + { + if (!Rfc9110::isToken($name)) { + throw new \InvalidArgumentException(sprintf('Invalid multipart part header name: %s', DiagnosticValue::escape($name))); + } + } + + private static function validatePartHeaderValue(string $name, string $value): void + { + if (!Rfc9110::isFieldValue($value)) { + $reason = strpbrk($value, "\r\n") !== false + ? 'must not contain CR or LF characters' + : 'contains an invalid control character'; + + throw new \InvalidArgumentException(sprintf('Multipart part header "%s" %s.', DiagnosticValue::escape($name), $reason)); + } + } + + private static function escapeContentDispositionParameter(string $value): string + { + // Match WHATWG browser multipart/form-data behavior: escape CR, LF, and DQUOTE only. + return str_replace(["\r", "\n", '"'], ['%0D', '%0A', '%22'], $value); + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php index 161a224f0..1611e3277 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -12,11 +12,11 @@ use Psr\Http\Message\StreamInterface; final class NoSeekStream implements StreamInterface { use StreamDecoratorTrait; + use NonSerializableStreamTrait; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { throw new \RuntimeException('Cannot seek a NoSeekStream'); } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php new file mode 100644 index 000000000..cc605cc99 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/NonSerializableStreamTrait.php @@ -0,0 +1,21 @@ +source = $source; - $this->size = $options['size'] ?? null; + $this->size = Integers::assertOptionalNonNegativeSize($options['size'] ?? null, 'Stream size'); $this->metadata = $options['metadata'] ?? []; $this->buffer = new BufferStream(); } + public function __unserialize(array $data): void + { + $this->source = null; + $this->size = null; + $this->tellPos = 0; + $this->metadata = []; + $this->buffer = new BufferStream(); + + throw new \LogicException(static::class.' should never be unserialized'); + } + public function __toString(): string { - try { - return Utils::copyToString($this); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - return ''; - } + return Utils::copyToString($this); } public function close(): void @@ -73,6 +81,7 @@ final class PumpStream implements StreamInterface { $this->tellPos = 0; $this->source = null; + $this->buffer->close(); return null; } @@ -102,7 +111,7 @@ final class PumpStream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { throw new \RuntimeException('Cannot seek a PumpStream'); } @@ -112,7 +121,7 @@ final class PumpStream implements StreamInterface return false; } - public function write($string): int + public function write(string $string): int { throw new \RuntimeException('Cannot write to a PumpStream'); } @@ -122,40 +131,35 @@ final class PumpStream implements StreamInterface return true; } - public function read($length): string + public function read(int $length): string { - $data = $this->buffer->read($length); - $readLen = strlen($data); - $this->tellPos += $readLen; - $remaining = $length - $readLen; - - if ($remaining) { - $this->pump($remaining); - $data .= $this->buffer->read($remaining); - $this->tellPos += strlen($data) - $readLen; + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); } + $bufferLength = $this->buffer->getSize() ?? 0; + + if ($length > $bufferLength) { + $this->pump($length - $bufferLength); + } + + $data = $this->buffer->read($length); + $this->tellPos = Integers::add($this->tellPos, strlen($data)); + return $data; } public function getContents(): string { - $result = ''; - while (!$this->eof()) { - $result .= $this->read(1000000); - } - - return $result; + return Utils::copyToString($this); } /** - * {@inheritdoc} - * * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { - if (!$key) { + if ($key === null) { return $this->metadata; } @@ -164,13 +168,20 @@ final class PumpStream implements StreamInterface private function pump(int $length): void { - if ($this->source) { + if ($this->source !== null) { do { - $data = call_user_func($this->source, $length); + /** @var string|false|null $data */ + $data = ($this->source)($length); if ($data === false || $data === null) { $this->source = null; + return; } + + if ($data === '') { + throw new \RuntimeException('PumpStream source returned an empty string'); + } + $this->buffer->write($data); $length -= strlen($data); } while ($length > 0); diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php index 2faab3a88..c54c669f2 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Query.php @@ -6,13 +6,17 @@ namespace GuzzleHttp\Psr7; final class Query { + private function __construct() + { + } + /** * Parse a query string into an associative array. * - * If multiple values are found for the same key, the value of that key - * value pair will become an array. This function does not parse nested - * PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` - * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + * If multiple values are found for the same key, the value of that + * key-value pair becomes an array. This function does not parse nested PHP + * style arrays into an associative array. For example, `foo[a]=1&foo[b]=2` + * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2']`. * * @param string $str Query string to parse * @param int|bool $urlEncoding How the query string is encoded @@ -26,15 +30,19 @@ final class Query } if ($urlEncoding === true) { - $decoder = function ($value) { - return rawurldecode(str_replace('+', ' ', (string) $value)); + $decoder = function (string $value): string { + return \rawurldecode(str_replace('+', ' ', $value)); }; } elseif ($urlEncoding === PHP_QUERY_RFC3986) { - $decoder = 'rawurldecode'; + $decoder = static function (string $value): string { + return \rawurldecode($value); + }; } elseif ($urlEncoding === PHP_QUERY_RFC1738) { - $decoder = 'urldecode'; + $decoder = static function (string $value): string { + return \urldecode($value); + }; } else { - $decoder = function ($str) { + $decoder = function (string $str): string { return $str; }; } @@ -57,18 +65,21 @@ final class Query } /** - * Build a query string from an array of key value pairs. + * Build a query string from an array of key-value pairs. * * This function can use the return value of `parse()` to build a query * string. This function does not modify the provided keys when an array is - * encountered (like `http_build_query()` would). + * encountered, unlike `http_build_query()`. * - * @param array $params Query string parameters. - * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 - * to encode using RFC3986, or PHP_QUERY_RFC1738 - * to encode using RFC1738. + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, + * PHP_QUERY_RFC3986 to encode using + * RFC3986, or PHP_QUERY_RFC1738 to + * encode using RFC1738. + * @param bool $treatBoolsAsInts Set to true to encode as 0/1, and + * false as false/true. */ - public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + public static function build(array $params, $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string { if (!$params) { return ''; @@ -79,9 +90,13 @@ final class Query return $str; }; } elseif ($encoding === PHP_QUERY_RFC3986) { - $encoder = 'rawurlencode'; + $encoder = static function (string $value): string { + return \rawurlencode($value); + }; } elseif ($encoding === PHP_QUERY_RFC1738) { - $encoder = 'urlencode'; + $encoder = static function (string $value): string { + return \urlencode($value); + }; } else { throw new \InvalidArgumentException('Invalid type'); } @@ -91,17 +106,17 @@ final class Query $k = $encoder((string) $k); if (!is_array($v)) { $qs .= $k; - $v = is_bool($v) ? (int) $v : $v; + $v = self::normalizeValue($v, $treatBoolsAsInts); if ($v !== null) { - $qs .= '=' . $encoder((string) $v); + $qs .= '='.$encoder($v); } $qs .= '&'; } else { foreach ($v as $vv) { $qs .= $k; - $vv = is_bool($vv) ? (int) $vv : $vv; + $vv = self::normalizeValue($vv, $treatBoolsAsInts); if ($vv !== null) { - $qs .= '=' . $encoder((string) $vv); + $qs .= '='.$encoder($vv); } $qs .= '&'; } @@ -110,4 +125,32 @@ final class Query return $qs ? (string) substr($qs, 0, -1) : ''; } + + /** + * @param mixed $value + */ + private static function normalizeValue($value, bool $treatBoolsAsInts): ?string + { + if ($value === null) { + return null; + } + + if (is_bool($value)) { + return $treatBoolsAsInts ? (string) (int) $value : ($value ? 'true' : 'false'); + } + + if (is_float($value) && !is_finite($value)) { + throw new \InvalidArgumentException('Query string values must be finite; non-finite floats are not supported.'); + } + + if (is_scalar($value)) { + return (string) $value; + } + + if (is_object($value) && method_exists($value, '__toString')) { + return $value->__toString(); + } + + throw new \InvalidArgumentException('Query string values must be scalar, null, or stringable objects'); + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ReadFailureCapturingStream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ReadFailureCapturingStream.php new file mode 100644 index 000000000..b3fe539d9 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ReadFailureCapturingStream.php @@ -0,0 +1,61 @@ +capturing) { + return $this->stream->read($length); + } + + try { + return $this->stream->read($length); + } catch (\Exception $e) { + $this->readFailure = $e; + + return ''; + } + } + + public function stopCapturing(): void + { + $this->capturing = false; + $this->readFailure = null; + } + + public function hasReadFailure(): bool + { + return $this->readFailure !== null; + } + + public function takeReadFailure(): ?\Exception + { + $failure = $this->readFailure; + $this->readFailure = null; + + return $failure; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php index b17af66a2..0875e40e0 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Request.php @@ -16,19 +16,16 @@ class Request implements RequestInterface { use MessageTrait; - /** @var string */ - private $method; + private string $method; - /** @var string|null */ - private $requestTarget; + private ?string $requestTarget = null; - /** @var UriInterface */ - private $uri; + private UriInterface $uri; /** * @param string $method HTTP method * @param string|UriInterface $uri URI - * @param array $headers Request headers + * @param (string|string[])[] $headers Request headers * @param string|resource|StreamInterface|null $body Request body * @param string $version Protocol version */ @@ -40,11 +37,14 @@ class Request implements RequestInterface string $version = '1.1' ) { $this->assertMethod($method); - if (!($uri instanceof UriInterface)) { + $this->assertProtocolVersion($version); + + if (!$uri instanceof UriInterface) { $uri = new Uri($uri); } + self::getRequestTargetFromUri($uri); - $this->method = strtoupper($method); + $this->method = $method; $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $version; @@ -64,27 +64,16 @@ class Request implements RequestInterface return $this->requestTarget; } - $target = $this->uri->getPath(); - if ($target === '') { - $target = '/'; - } - if ($this->uri->getQuery() != '') { - $target .= '?' . $this->uri->getQuery(); - } - - return $target; + return self::getRequestTargetFromUri($this->uri); } - public function withRequestTarget($requestTarget): RequestInterface + public function withRequestTarget(string $requestTarget): RequestInterface { - if (preg_match('#\s#', $requestTarget)) { - throw new InvalidArgumentException( - 'Invalid request target provided; cannot contain whitespace' - ); - } + self::assertRequestTarget($requestTarget); $new = clone $this; $new->requestTarget = $requestTarget; + return $new; } @@ -93,11 +82,12 @@ class Request implements RequestInterface return $this->method; } - public function withMethod($method): RequestInterface + public function withMethod(string $method): RequestInterface { $this->assertMethod($method); $new = clone $this; - $new->method = strtoupper($method); + $new->method = $method; + return $new; } @@ -106,17 +96,30 @@ class Request implements RequestInterface return $this->uri; } - public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface { - if ($uri === $this->uri) { + $sameUri = $uri === $this->uri; + + if (!$sameUri && $this->requestTarget === null) { + self::getRequestTargetFromUri($uri); + } + + $currentHost = $this->getHeaderLine('Host'); + $host = null; + + if (!$preserveHost || $currentHost === '') { + $host = $this->getHostFromUri($uri); + } + + if ($sameUri && ($host === null || $currentHost === $host)) { return $this; } $new = clone $this; $new->uri = $uri; - if (!$preserveHost || !isset($this->headerNames['host'])) { - $new->updateHostFromUri(); + if ($host !== null) { + $new->setHostHeader($host); } return $new; @@ -124,16 +127,36 @@ class Request implements RequestInterface private function updateHostFromUri(): void { - $host = $this->uri->getHost(); + $host = $this->getHostFromUri($this->uri); - if ($host == '') { + if ($host === null) { return; } - if (($port = $this->uri->getPort()) !== null) { - $host .= ':' . $port; + $this->setHostHeader($host); + } + + private function getHostFromUri(UriInterface $uri): ?string + { + $host = $uri->getHost(); + + if ($host === '') { + return null; } + Uri::assertValidHost($host); + + if (($port = $uri->getPort()) !== null) { + $host .= ':'.$port; + } + + $this->assertValue('Host', $host); + + return $host; + } + + private function setHostHeader(string $host): void + { if (isset($this->headerNames['host'])) { $header = $this->headerNames['host']; } else { @@ -141,17 +164,47 @@ class Request implements RequestInterface $this->headerNames['host'] = 'Host'; } // Ensure Host is the first header. - // See: http://tools.ietf.org/html/rfc7230#section-5.4 + // See: https://datatracker.ietf.org/doc/html/rfc9110#section-7.2 $this->headers = [$header => [$host]] + $this->headers; } - /** - * @param mixed $method - */ - private function assertMethod($method): void + private function assertMethod(string $method): void { - if (!is_string($method) || $method === '') { - throw new InvalidArgumentException('Method must be a non-empty string.'); + if (!Rfc9110::isToken($method)) { + throw new InvalidArgumentException('Method must be a valid HTTP token.'); } } + + private static function getRequestTargetFromUri(UriInterface $uri): string + { + $target = self::normalizePathForOriginForm($uri->getPath()); + if ($target === '') { + $target = '/'; + } + if ($uri->getQuery() != '') { + $target .= '?'.$uri->getQuery(); + } + + self::assertRequestTarget($target); + + return $target; + } + + private static function assertRequestTarget(string $requestTarget): void + { + if (!Rfc9112::isValidRequestTarget($requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot be empty or contain whitespace or control characters' + ); + } + } + + private static function normalizePathForOriginForm(string $path): string + { + if (str_starts_with($path, '//')) { + return '/'.ltrim($path, '/'); + } + + return $path; + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php index 4c6ee6f03..a4e8ff664 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Response.php @@ -78,15 +78,13 @@ class Response implements ResponseInterface 511 => 'Network Authentication Required', ]; - /** @var string */ - private $reasonPhrase; + private string $reasonPhrase; - /** @var int */ - private $statusCode; + private int $statusCode; /** * @param int $status Status code - * @param array $headers Response headers + * @param (string|string[])[] $headers Response headers * @param string|resource|StreamInterface|null $body Response body * @param string $version Protocol version * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) @@ -96,9 +94,10 @@ class Response implements ResponseInterface array $headers = [], $body = null, string $version = '1.1', - string $reason = null + ?string $reason = null ) { $this->assertStatusCodeRange($status); + $this->assertProtocolVersion($version); $this->statusCode = $status; @@ -108,11 +107,14 @@ class Response implements ResponseInterface $this->setHeaders($headers); if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { - $this->reasonPhrase = self::PHRASES[$this->statusCode]; + $reasonPhrase = self::PHRASES[$this->statusCode]; } else { - $this->reasonPhrase = (string) $reason; + $reasonPhrase = (string) $reason; } + $this->assertReasonPhrase($reasonPhrase); + $this->reasonPhrase = $reasonPhrase; + $this->protocol = $version; } @@ -126,29 +128,19 @@ class Response implements ResponseInterface return $this->reasonPhrase; } - public function withStatus($code, $reasonPhrase = ''): ResponseInterface + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface { - $this->assertStatusCodeIsInteger($code); - $code = (int) $code; $this->assertStatusCodeRange($code); $new = clone $this; $new->statusCode = $code; - if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + if ($reasonPhrase === '' && isset(self::PHRASES[$new->statusCode])) { $reasonPhrase = self::PHRASES[$new->statusCode]; } - $new->reasonPhrase = (string) $reasonPhrase; - return $new; - } + $this->assertReasonPhrase($reasonPhrase); + $new->reasonPhrase = $reasonPhrase; - /** - * @param mixed $statusCode - */ - private function assertStatusCodeIsInteger($statusCode): void - { - if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { - throw new \InvalidArgumentException('Status code must be an integer value.'); - } + return $new; } private function assertStatusCodeRange(int $statusCode): void @@ -157,4 +149,11 @@ class Response implements ResponseInterface throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); } } + + private function assertReasonPhrase(string $reasonPhrase): void + { + if (!Rfc9112::isValidReasonPhrase($reasonPhrase)) { + throw new \InvalidArgumentException('Reason phrase must not contain invalid control characters.'); + } + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc3986.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc3986.php new file mode 100644 index 000000000..d6cc92aeb --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc3986.php @@ -0,0 +1,361 @@ + $word) { + if ($word !== 0) { + $start = -1; + continue; + } + if ($start === -1) { + $start = $i; + } + if ($i - $start + 1 > $bestLen) { + $bestStart = $start; + $bestLen = $i - $start + 1; + } + } + if ($bestLen < 2) { + $bestLen = 0; + } + + // RFC 5952 section 5: embedded IPv4 notation for IPv4-mapped + // (::ffff:0:0/96) and IPv4-compatible (::/96) addresses, the same + // condition BIND-derived inet_ntop() and curl use. bestStart must be + // zero: a five or six field zero run elsewhere is not an IPv4 prefix. + $mixed = $bestStart === 0 + && ($bestLen === 6 || ($bestLen === 5 && $words[5] === 0xFFFF)); + + $groups = []; + for ($i = 0, $n = $mixed ? 6 : 8; $i < $n; ++$i) { + $groups[] = dechex($words[$i]); + } + if ($mixed) { + $groups[] = sprintf( + '%d.%d.%d.%d', + $words[6] >> 8, + $words[6] & 0xFF, + $words[7] >> 8, + $words[7] & 0xFF + ); + } + + if ($bestLen === 0) { + return implode(':', $groups); + } + + return implode(':', array_slice($groups, 0, $bestStart)).'::'.implode(':', array_slice($groups, $bestStart + $bestLen)); + } + + private static function hasValidHostPercentEncoding(string $host): bool + { + // Mirror of the raw reg-name policy above for percent-encoded octets: + // reject malformed sequences (RFC 3986 requires "%" HEXDIG HEXDIG) and + // octets that decode to bytes the raw grammar rejects - C0 controls, + // SP, DEL, the delimiters / ? # @ \ [ ], the port colon, and % itself. + // Octets decoding to any other byte (unreserved, sub-delims, and + // non-ASCII UTF-8 data) remain accepted. + $invalidEncoding = preg_match( + '/%(?!'.self::HEX_OCTET.')|%(?:[01][0-9A-Fa-f]|2[035F]|3[AF]|40|5[BCD]|7F)/i', + $host + ); + + return $invalidEncoding === 0; + } + + private static function isValidIpLiteralHost(string $host): bool + { + if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) { + return false; + } + + $address = substr($host, 1, -1); + if (\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false) { + return true; + } + + // RFC 6874 IPv6 zone identifiers are intentionally not supported here. + // Bracketed hosts are validated as IPv6 or IPvFuture only. + return preg_match('/^v[0-9a-f]+\.['.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1; + } + + /** + * Parses a textual IPv6 address into its eight 16-bit words, or null + * when the text is not a structurally valid address. + * + * The grammar enforced here is the RFC 3986 `IPv6address` rule: one to four + * hexadecimal digits per field, at most one `::` eliding one or more zero + * fields, and an optional dotted-decimal tail of four octets (0-255, no + * leading zeros) as the final 32 bits. FILTER_VALIDATE_IP accepts exactly + * this grammar, so the filter guard in tryCanonicalizeIpv6() and this + * parser always agree and the null paths here can only fail closed. + * + * @return list|null + */ + private static function parseIpv6Words(string $address): ?array + { + // A dotted-decimal tail is only valid as the final 32 bits, after the + // final colon. Rewrite it into its two hexadecimal fields so the + // remainder of the parse handles hexadecimal fields only. + $dot = strpos($address, '.'); + if ($dot !== false) { + $colon = strrpos($address, ':'); + if ($colon === false || $colon > $dot) { + return null; + } + $octets = explode('.', substr($address, $colon + 1)); + if (count($octets) !== 4) { + return null; + } + $bytes = []; + foreach ($octets as $octet) { + if ($octet === '' || strlen($octet) > 3 || !ctype_digit($octet)) { + return null; + } + if ($octet[0] === '0' && $octet !== '0') { + return null; + } + $byte = (int) $octet; + if ($byte > 255) { + return null; + } + $bytes[] = $byte; + } + $address = substr($address, 0, $colon + 1) + .dechex(($bytes[0] << 8) | $bytes[1]) + .':' + .dechex(($bytes[2] << 8) | $bytes[3]); + } + + $halves = explode('::', $address); + if (count($halves) > 2) { + return null; + } + + $head = self::parseHexFields($halves[0]); + if ($head === null) { + return null; + } + + if (count($halves) === 1) { + return count($head) === 8 ? $head : null; + } + + $tail = self::parseHexFields($halves[1]); + if ($tail === null) { + return null; + } + + // The "::" must elide at least one zero field. + $elided = 8 - count($head) - count($tail); + if ($elided < 1) { + return null; + } + + return array_merge($head, array_fill(0, $elided, 0), $tail); + } + + /** + * Parses a colon-separated run of 16-bit hexadecimal fields, or null + * when a field is empty, longer than four digits, or not hexadecimal. + * + * @return list|null + */ + private static function parseHexFields(string $fields): ?array + { + if ($fields === '') { + return []; + } + + $words = []; + foreach (explode(':', $fields) as $field) { + if ($field === '' || strlen($field) > 4 || !ctype_xdigit($field)) { + return null; + } + $words[] = intval($field, 16); + } + + return $words; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc7230.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc7230.php deleted file mode 100644 index 30224018d..000000000 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc7230.php +++ /dev/null @@ -1,23 +0,0 @@ -@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; - public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; -} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc9110.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc9110.php new file mode 100644 index 000000000..c93b1b2fd --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Rfc9110.php @@ -0,0 +1,41 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; + + public static function isValidProtocolVersion(string $version): bool + { + return preg_match('/^'.self::PROTOCOL_VERSION_PATTERN.'$/D', $version) === 1; + } + + public static function isValidRequestTarget(string $target): bool + { + return preg_match('/^'.self::REQUEST_TARGET_PATTERN.'$/D', $target) === 1; + } + + public static function isValidReasonPhrase(string $reasonPhrase): bool + { + return Rfc9110::isFieldValue($reasonPhrase); + } + + /** + * @return array{0: string, 1: int|null}|null + */ + public static function parseHostHeader(string $authority): ?array + { + if ($authority === '') { + return null; + } + + $host = $authority; + $port = null; + + if (str_starts_with($authority, '[')) { + $closingBracket = strpos($authority, ']'); + if ($closingBracket === false) { + return null; + } + + $host = substr($authority, 0, $closingBracket + 1); + $remainder = substr($authority, $closingBracket + 1); + if ($remainder !== '') { + if (!str_starts_with($remainder, ':')) { + return null; + } + + $port = self::parsePort(substr($remainder, 1)); + if ($port === null) { + return null; + } + } + } elseif (false !== ($colon = strpos($authority, ':'))) { + $host = substr($authority, 0, $colon); + $port = self::parsePort(substr($authority, $colon + 1)); + if ($port === null) { + return null; + } + } + + if ($host === '' || !Rfc3986::isValidHost($host)) { + return null; + } + + return [$host, $port]; + } + + public static function isAbsoluteFormRequestTarget(string $target): bool + { + return preg_match('/^[A-Za-z][A-Za-z0-9+.-]*:\/\//D', $target) === 1; + } + + public static function isAsteriskFormRequestTarget(string $method, string $target): bool + { + return $method === 'OPTIONS' && $target === '*'; + } + + public static function isConnectAuthorityFormRequestTarget(string $method, string $target): bool + { + return $method === 'CONNECT' && strpbrk($target, '/?#') === false; + } + + public static function parsePort(string $port): ?int + { + if (!Rfc3986::isValidPort($port)) { + return null; + } + + // A zero port is valid per RFC 3986 but meaningless for an HTTP + // authority, so reject it on top of the generic syntax check. + $parsed = (int) ltrim($port, '0'); + + return $parsed === 0 ? null : $parsed; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php index b2aa382da..adf17627d 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -26,40 +26,25 @@ use Psr\Http\Message\UriInterface; */ class ServerRequest extends Request implements ServerRequestInterface { - /** - * @var array - */ - private $attributes = []; + private array $attributes = []; - /** - * @var array - */ - private $cookieParams = []; + private array $cookieParams = []; /** * @var array|object|null */ private $parsedBody; - /** - * @var array - */ - private $queryParams = []; + private array $queryParams = []; - /** - * @var array - */ - private $serverParams; + private array $serverParams; - /** - * @var array - */ - private $uploadedFiles = []; + private array $uploadedFiles = []; /** * @param string $method HTTP method * @param string|UriInterface $uri URI - * @param array $headers Request headers + * @param (string|string[])[] $headers Request headers * @param string|resource|StreamInterface|null $body Request body * @param string $version Protocol version * @param array $serverParams Typically the $_SERVER superglobal @@ -70,6 +55,7 @@ class ServerRequest extends Request implements ServerRequestInterface array $headers = [], $body = null, string $version = '1.1', + #[\SensitiveParameter] array $serverParams = [] ) { $this->serverParams = $serverParams; @@ -86,73 +72,7 @@ class ServerRequest extends Request implements ServerRequestInterface */ public static function normalizeFiles(array $files): array { - $normalized = []; - - foreach ($files as $key => $value) { - if ($value instanceof UploadedFileInterface) { - $normalized[$key] = $value; - } elseif (is_array($value) && isset($value['tmp_name'])) { - $normalized[$key] = self::createUploadedFileFromSpec($value); - } elseif (is_array($value)) { - $normalized[$key] = self::normalizeFiles($value); - continue; - } else { - throw new InvalidArgumentException('Invalid value in files specification'); - } - } - - return $normalized; - } - - /** - * Create and return an UploadedFile instance from a $_FILES specification. - * - * If the specification represents an array of values, this method will - * delegate to normalizeNestedFileSpec() and return that return value. - * - * @param array $value $_FILES struct - * - * @return UploadedFileInterface|UploadedFileInterface[] - */ - private static function createUploadedFileFromSpec(array $value) - { - if (is_array($value['tmp_name'])) { - return self::normalizeNestedFileSpec($value); - } - - return new UploadedFile( - $value['tmp_name'], - (int) $value['size'], - (int) $value['error'], - $value['name'], - $value['type'] - ); - } - - /** - * Normalize an array of file specifications. - * - * Loops through all nested files and returns a normalized array of - * UploadedFileInterface instances. - * - * @return UploadedFileInterface[] - */ - private static function normalizeNestedFileSpec(array $files = []): array - { - $normalizedFiles = []; - - foreach (array_keys($files['tmp_name']) as $key) { - $spec = [ - 'tmp_name' => $files['tmp_name'][$key], - 'size' => $files['size'][$key] ?? null, - 'error' => $files['error'][$key] ?? null, - 'name' => $files['name'][$key] ?? null, - 'type' => $files['type'][$key] ?? null, - ]; - $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); - } - - return $normalizedFiles; + return UploadedFileNormalizer::normalize($files); } /** @@ -165,33 +85,20 @@ class ServerRequest extends Request implements ServerRequestInterface */ public static function fromGlobals(): ServerRequestInterface { - $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; - $headers = getallheaders(); - $uri = self::getUriFromGlobals(); - $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); - $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; + return ServerRequestGlobalsFactory::fromArrays( + $_SERVER, + $_GET, + $_POST, + $_COOKIE, + $_FILES, + static function () { + if (!\function_exists('apache_request_headers')) { + return false; + } - $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); - - return $serverRequest - ->withCookieParams($_COOKIE) - ->withQueryParams($_GET) - ->withParsedBody($_POST) - ->withUploadedFiles(self::normalizeFiles($_FILES)); - } - - private static function extractHostAndPortFromAuthority(string $authority): array - { - $uri = 'http://' . $authority; - $parts = parse_url($uri); - if (false === $parts) { - return [null, null]; - } - - $host = $parts['host'] ?? null; - $port = $parts['port'] ?? null; - - return [$host, $port]; + return \apache_request_headers(); + } + ); } /** @@ -199,46 +106,7 @@ class ServerRequest extends Request implements ServerRequestInterface */ public static function getUriFromGlobals(): UriInterface { - $uri = new Uri(''); - - $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); - - $hasPort = false; - if (isset($_SERVER['HTTP_HOST'])) { - [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); - if ($host !== null) { - $uri = $uri->withHost($host); - } - - if ($port !== null) { - $hasPort = true; - $uri = $uri->withPort($port); - } - } elseif (isset($_SERVER['SERVER_NAME'])) { - $uri = $uri->withHost($_SERVER['SERVER_NAME']); - } elseif (isset($_SERVER['SERVER_ADDR'])) { - $uri = $uri->withHost($_SERVER['SERVER_ADDR']); - } - - if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { - $uri = $uri->withPort($_SERVER['SERVER_PORT']); - } - - $hasQuery = false; - if (isset($_SERVER['REQUEST_URI'])) { - $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); - $uri = $uri->withPath($requestUriParts[0]); - if (isset($requestUriParts[1])) { - $hasQuery = true; - $uri = $uri->withQuery($requestUriParts[1]); - } - } - - if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { - $uri = $uri->withQuery($_SERVER['QUERY_STRING']); - } - - return $uri; + return ServerRequestGlobalsFactory::getUriFromServerParams($_SERVER); } public function getServerParams(): array @@ -253,6 +121,26 @@ class ServerRequest extends Request implements ServerRequestInterface public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { + $stack = [$uploadedFiles]; + + for ($i = 0; $i < \count($stack); ++$i) { + foreach ($stack[$i] as $uploadedFile) { + if ($uploadedFile instanceof UploadedFileInterface) { + continue; + } + + if (\is_array($uploadedFile)) { + $stack[] = $uploadedFile; + continue; + } + + throw new InvalidArgumentException(sprintf( + 'Invalid uploaded file tree; expected UploadedFileInterface instances but %s provided.', + \get_debug_type($uploadedFile) + )); + } + } + $new = clone $this; $new->uploadedFiles = $uploadedFiles; @@ -264,8 +152,10 @@ class ServerRequest extends Request implements ServerRequestInterface return $this->cookieParams; } - public function withCookieParams(array $cookies): ServerRequestInterface - { + public function withCookieParams( + #[\SensitiveParameter] + array $cookies + ): ServerRequestInterface { $new = clone $this; $new->cookieParams = $cookies; @@ -286,8 +176,6 @@ class ServerRequest extends Request implements ServerRequestInterface } /** - * {@inheritdoc} - * * @return array|object|null */ public function getParsedBody() @@ -297,6 +185,10 @@ class ServerRequest extends Request implements ServerRequestInterface public function withParsedBody($data): ServerRequestInterface { + if ($data !== null && !\is_array($data) && !\is_object($data)) { + throw new InvalidArgumentException('Parsed body must be an array, object, or null.'); + } + $new = clone $this; $new->parsedBody = $data; @@ -309,35 +201,33 @@ class ServerRequest extends Request implements ServerRequestInterface } /** - * {@inheritdoc} - * * @return mixed */ - public function getAttribute($attribute, $default = null) + public function getAttribute(string $name, $default = null) { - if (false === array_key_exists($attribute, $this->attributes)) { + if (false === array_key_exists($name, $this->attributes)) { return $default; } - return $this->attributes[$attribute]; + return $this->attributes[$name]; } - public function withAttribute($attribute, $value): ServerRequestInterface + public function withAttribute(string $name, $value): ServerRequestInterface { $new = clone $this; - $new->attributes[$attribute] = $value; + $new->attributes[$name] = $value; return $new; } - public function withoutAttribute($attribute): ServerRequestInterface + public function withoutAttribute(string $name): ServerRequestInterface { - if (false === array_key_exists($attribute, $this->attributes)) { + if (false === array_key_exists($name, $this->attributes)) { return $this; } $new = clone $this; - unset($new->attributes[$attribute]); + unset($new->attributes[$name]); return $new; } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php new file mode 100644 index 000000000..977420fca --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/ServerRequestGlobalsFactory.php @@ -0,0 +1,504 @@ + $server Typically the $_SERVER superglobal + * @param array $query Typically the $_GET superglobal + * @param array $post Typically the $_POST superglobal + * @param array $cookies Typically the $_COOKIE superglobal + * @param array $files Typically the $_FILES superglobal + * @param (callable(): mixed)|null $headerProvider + */ + public static function fromArrays( + #[\SensitiveParameter] + array $server, + array $query, + array $post, + #[\SensitiveParameter] + array $cookies, + array $files, + ?callable $headerProvider = null + ): ServerRequestInterface { + $method = self::getRequestMethodFromServer($server); + $headers = self::removeInvalidHostHeader(self::getAllHeaders($server, $headerProvider)); + [$uri, $requestTarget] = self::getUriAndRequestTargetFromServer($server, $method); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + + $serverRequest = new ServerRequest($method, $uri, $headers, $body, self::getProtocolFromServer($server), $server); + if ($requestTarget !== null) { + /** @var ServerRequestInterface $serverRequest */ + $serverRequest = $serverRequest->withRequestTarget($requestTarget); + } + + return $serverRequest + ->withCookieParams($cookies) + ->withQueryParams($query) + ->withParsedBody($post) + ->withUploadedFiles(UploadedFileNormalizer::normalize($files)); + } + + /** + * @param array $server Typically the $_SERVER superglobal + */ + public static function getUriFromServerParams( + #[\SensitiveParameter] + array $server + ): UriInterface { + $method = self::getRequestMethodFromServer($server); + + return self::getUriAndRequestTargetFromServer($server, $method)[0]; + } + + /** + * @param array $server + * @param (callable(): mixed)|null $headerProvider + * + * @return array + */ + private static function getAllHeaders( + #[\SensitiveParameter] + array $server, + ?callable $headerProvider + ): array { + $headers = $headerProvider !== null ? $headerProvider() : false; + + if (!is_array($headers)) { + $headers = self::getHeadersFromServer($server); + } + + return self::normalizeHeaderValues($headers); + } + + /** + * @param array $headers + * + * @return array + */ + private static function normalizeHeaderValues( + #[\SensitiveParameter] + array $headers + ): array { + $normalized = []; + + foreach ($headers as $name => $value) { + if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { + $normalized[$name] = (string) $value; + } + } + + return $normalized; + } + + /** + * @param array $server Typically the $_SERVER superglobal + * + * @return array + */ + private static function getHeadersFromServer(array $server): array + { + $headers = []; + + $copyServer = [ + 'CONTENT_TYPE' => 'Content-Type', + 'CONTENT_LENGTH' => 'Content-Length', + 'CONTENT_MD5' => 'Content-Md5', + ]; + + foreach ($server as $key => $value) { + if (!is_string($key) || !is_string($value)) { + continue; + } + + if (str_starts_with($key, 'HTTP_')) { + $header = substr($key, 5); + + if (isset($copyServer[$header], $server[$header]) && is_string($server[$header])) { + continue; + } + + $parts = explode(' ', Utils::asciiToLower(str_replace('_', ' ', $header))); + foreach ($parts as $i => $part) { + $parts[$i] = Utils::asciiUcFirst($part); + } + $header = implode('-', $parts); + $headers[$header] = $value; + + continue; + } + + if (isset($copyServer[$key])) { + $headers[$copyServer[$key]] = $value; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($server['REDIRECT_HTTP_AUTHORIZATION']) && is_string($server['REDIRECT_HTTP_AUTHORIZATION'])) { + $headers['Authorization'] = $server['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($server['PHP_AUTH_USER']) && is_string($server['PHP_AUTH_USER'])) { + $password = isset($server['PHP_AUTH_PW']) && is_string($server['PHP_AUTH_PW']) + ? $server['PHP_AUTH_PW'] + : ''; + + $headers['Authorization'] = 'Basic '.base64_encode($server['PHP_AUTH_USER'].':'.$password); + } elseif (isset($server['PHP_AUTH_DIGEST']) && is_string($server['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = $server['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + + /** + * @param array $headers + * + * @return array + */ + private static function removeInvalidHostHeader(array $headers): array + { + foreach ($headers as $name => $value) { + if (Utils::asciiToLower((string) $name) !== 'host') { + continue; + } + + [$host] = self::extractHostAndPortFromAuthority($value); + if ($host === null) { + unset($headers[$name]); + } + } + + return $headers; + } + + /** + * @param array $server + */ + private static function getServerParam(array $server, string $key): ?string + { + return isset($server[$key]) && is_string($server[$key]) ? $server[$key] : null; + } + + /** + * @param array $server + */ + private static function getRequestMethodFromServer(array $server): string + { + return Utils::asciiToUpper(self::getServerParam($server, 'REQUEST_METHOD') ?? 'GET'); + } + + /** + * @param array $server + */ + private static function getProtocolFromServer(array $server): string + { + $serverProtocol = self::getServerParam($server, 'SERVER_PROTOCOL'); + if ($serverProtocol === null) { + return '1.1'; + } + + return str_starts_with($serverProtocol, 'HTTP/') ? substr($serverProtocol, 5) : $serverProtocol; + } + + /** + * @return array{0: string|null, 1: int|null} + */ + private static function extractHostAndPortFromAuthority(string $authority): array + { + return Rfc9112::parseHostHeader($authority) ?? [null, null]; + } + + private static function parseServerPort(string $port): int + { + $parsed = Rfc9112::parsePort($port); + if ($parsed === null) { + throw new InvalidArgumentException('Invalid SERVER_PORT; expected an integer between 1 and 65535.'); + } + + return $parsed; + } + + private static function withHostFromServer(UriInterface $uri, ?string $host): ?UriInterface + { + if ($host === null) { + return null; + } + + try { + return $uri->withHost($host); + } catch (InvalidArgumentException $e) { + return null; + } + } + + /** + * @param array $server + */ + private static function getUriWithSchemeFromServer(array $server): UriInterface + { + $uri = new Uri(''); + + $https = self::getServerParam($server, 'HTTPS'); + + return $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http'); + } + + /** + * @param array $server + */ + private static function getAuthorityUriFromServer( + #[\SensitiveParameter] + array $server + ): UriInterface { + $uri = self::getUriWithSchemeFromServer($server); + + $hasPort = false; + $hasHost = false; + $authority = self::getServerParam($server, 'HTTP_HOST'); + if ($authority !== null) { + [$host, $port] = self::extractHostAndPortFromAuthority($authority); + if ($host !== null) { + $hostUri = self::withHostFromServer($uri, $host); + if ($hostUri !== null) { + $uri = $hostUri; + $hasHost = true; + + if ($port !== null) { + $hasPort = true; + $uri = $uri->withPort($port); + } + } + } + } + + foreach (['SERVER_NAME', 'SERVER_ADDR'] as $serverParam) { + if ($hasHost) { + continue; + } + + $hostUri = self::withHostFromServer($uri, self::getServerParam($server, $serverParam)); + if ($hostUri !== null) { + $uri = $hostUri; + $hasHost = true; + } + } + + $serverPort = self::getServerParam($server, 'SERVER_PORT'); + if (!$hasPort && $serverPort !== null) { + $uri = $uri->withPort(self::parseServerPort($serverPort)); + } + + return $uri; + } + + /** + * @param array $server + * + * @return array{0: UriInterface, 1: string|null} + */ + private static function getUriAndRequestTargetFromServer( + #[\SensitiveParameter] + array $server, + string $method + ): array { + $requestUri = self::getServerParam($server, 'REQUEST_URI'); + $queryString = self::getServerParam($server, 'QUERY_STRING'); + + if ($requestUri !== null) { + $connectAuthority = self::parseConnectAuthorityFormRequestTarget($method, $requestUri); + if ($connectAuthority !== null) { + [$host, $port] = $connectAuthority; + $uri = self::getUriWithSchemeFromServer($server); + + return [ + $uri->withHost($host)->withPort($port)->withPath('')->withQuery(''), + $requestUri, + ]; + } + + $absoluteForm = self::getAbsoluteFormUriAndRequestTarget($requestUri, $queryString); + if ($absoluteForm !== null) { + return $absoluteForm; + } + } + + $uri = self::getAuthorityUriFromServer($server); + + if ($requestUri === null) { + if ($queryString !== null) { + $uri = $uri->withQuery($queryString); + } + + return [$uri, null]; + } + + if (Rfc9112::isAsteriskFormRequestTarget($method, $requestUri)) { + return [$uri->withPath('')->withQuery(''), '*']; + } + + [$path, $query, $hasQuery] = self::splitRequestTargetQuery($requestUri); + $uri = $uri->withPath(self::normalizeOriginFormPathFromServer($path)); + + if ($hasQuery) { + $uri = $uri->withQuery($query); + } elseif ($queryString !== null) { + $uri = $uri->withQuery($queryString); + } + + return [$uri, null]; + } + + /** + * @return array{0: UriInterface, 1: string}|null + */ + private static function getAbsoluteFormUriAndRequestTarget( + #[\SensitiveParameter] + string $requestUri, + ?string $queryString + ): ?array { + if (!Rfc9112::isAbsoluteFormRequestTarget($requestUri)) { + return null; + } + + try { + $targetUri = (new Uri($requestUri))->withFragment(''); + } catch (InvalidArgumentException $e) { + return null; + } + + if ($targetUri->getHost() === '') { + return null; + } + + $requestTarget = self::removeRequestTargetFragment($requestUri); + $requestTargetWithoutUserInfo = self::removeUserInfoFromAbsoluteFormRequestTarget($requestTarget); + if ($requestTargetWithoutUserInfo !== $requestTarget) { + $targetUri = $targetUri->withUserInfo(''); + $requestTarget = $requestTargetWithoutUserInfo; + } + + if (!str_contains($requestTarget, '?') && $queryString !== null && $queryString !== '') { + $targetUri = $targetUri->withQuery($queryString); + $requestTarget .= '?'.$queryString; + } + + // Preserve the received absolute-form target unless it cannot be used as + // a PSR-7 request target without normalization. + $normalizeRequestTarget = !Rfc9112::isValidRequestTarget($requestTarget) + || self::hasEmptyPortInAbsoluteFormRequestTarget($requestTarget); + + return [$targetUri, $normalizeRequestTarget ? (string) $targetUri : $requestTarget]; + } + + private static function removeUserInfoFromAbsoluteFormRequestTarget(string $target): string + { + $authorityStart = strpos($target, '://'); + if ($authorityStart === false) { + return $target; + } + + $authorityStart += 3; + $authorityLength = strcspn($target, '/?#', $authorityStart); + $authority = substr($target, $authorityStart, $authorityLength); + if ($authority === '') { + return $target; + } + + $lastAt = strrpos($authority, '@'); + if ($lastAt === false) { + return $target; + } + + $authorityEnd = $authorityStart + $authorityLength; + + return substr($target, 0, $authorityStart) + .substr($authority, $lastAt + 1) + .substr($target, $authorityEnd); + } + + private static function hasEmptyPortInAbsoluteFormRequestTarget(string $target): bool + { + $authorityStart = strpos($target, '://'); + if ($authorityStart === false) { + return false; + } + + $authorityStart += 3; + $authority = substr($target, $authorityStart, strcspn($target, '/?#', $authorityStart)); + if ($authority === '') { + return false; + } + + $lastAt = strrpos($authority, '@'); + if ($lastAt !== false) { + $authority = substr($authority, $lastAt + 1); + } + + if ($authority === '') { + return false; + } + + if (str_starts_with($authority, '[')) { + $closingBracket = strpos($authority, ']'); + + return $closingBracket !== false && substr($authority, $closingBracket + 1) === ':'; + } + + return str_ends_with($authority, ':'); + } + + /** + * @return array{0: string, 1: int}|null + */ + private static function parseConnectAuthorityFormRequestTarget(string $method, string $target): ?array + { + if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) { + return null; + } + + [$host, $port] = self::extractHostAndPortFromAuthority($target); + if ($host === null || $port === null) { + return null; + } + + return [$host, $port]; + } + + private static function removeRequestTargetFragment(string $target): string + { + return explode('#', $target, 2)[0]; + } + + /** + * @return array{0: string, 1: string, 2: bool} + */ + private static function splitRequestTargetQuery(string $target): array + { + $parts = explode('?', $target, 2); + + return [$parts[0], $parts[1] ?? '', isset($parts[1])]; + } + + private static function normalizeOriginFormPathFromServer(string $path): string + { + if ($path === '' || str_starts_with($path, '/')) { + return $path; + } + + return '/'.$path; + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php index ecd31861e..4235993a1 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Stream.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\StreamInterface; /** @@ -11,27 +12,17 @@ use Psr\Http\Message\StreamInterface; */ class Stream implements StreamInterface { - /** - * @see http://php.net/manual/function.fopen.php - * @see http://php.net/manual/en/function.gzopen.php - */ - private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/'; - private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/'; + use NonSerializableStreamTrait; /** @var resource */ private $stream; - /** @var int|null */ - private $size; - /** @var bool */ - private $seekable; - /** @var bool */ - private $readable; - /** @var bool */ - private $writable; - /** @var string|null */ - private $uri; + private ?int $size = null; + private bool $seekable; + private bool $readable; + private bool $writable; + private ?string $uri = null; /** @var mixed[] */ - private $customMetadata; + private array $customMetadata; /** * This constructor accepts an associative array of options. @@ -53,17 +44,15 @@ class Stream implements StreamInterface throw new \InvalidArgumentException('Stream must be a resource'); } - if (isset($options['size'])) { - $this->size = $options['size']; - } + $this->size = Integers::assertOptionalNonNegativeSize($options['size'] ?? null, 'Stream size'); $this->customMetadata = $options['metadata'] ?? []; $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; - $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']); - $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']); - $this->uri = $this->getMetadata('uri'); + $this->readable = self::isReadableMode($meta['mode']); + $this->writable = self::isWritableMode($meta['mode']); + $this->uri = $meta['uri'] ?? null; } /** @@ -76,18 +65,11 @@ class Stream implements StreamInterface public function __toString(): string { - try { - if ($this->isSeekable()) { - $this->seek(0); - } - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - return ''; + if ($this->isSeekable()) { + $this->seek(0); } + + return $this->getContents(); } public function getContents(): string @@ -143,12 +125,13 @@ class Stream implements StreamInterface } $stats = fstat($this->stream); - if (is_array($stats) && isset($stats['size'])) { - $this->size = $stats['size']; - return $this->size; + if ($stats === false) { + return null; } - return null; + $this->size = Integers::assertEngineInteger($stats['size'], 'Stream size'); + + return $this->size; } public function isReadable(): bool @@ -182,12 +165,16 @@ class Stream implements StreamInterface } $result = ftell($this->stream); - if ($result === false) { throw new \RuntimeException('Unable to determine stream position'); } - return $result; + $position = Integers::assertEngineInteger($result, 'Stream position'); + if ($position === null) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $position; } public function rewind(): void @@ -195,10 +182,8 @@ class Stream implements StreamInterface $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { - $whence = (int) $whence; - if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } @@ -207,11 +192,11 @@ class Stream implements StreamInterface } if (fseek($this->stream, $offset, $whence) === -1) { throw new \RuntimeException('Unable to seek to stream position ' - . $offset . ' with whence ' . var_export($whence, true)); + .$offset.' with whence '.var_export($whence, true)); } } - public function read($length): string + public function read(int $length): string { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); @@ -229,18 +214,32 @@ class Stream implements StreamInterface try { $string = fread($this->stream, $length); + } catch (TimeoutException $e) { + throw $e; } catch (\Exception $e) { + if ($this->timedOut()) { + throw new TimeoutException('Unable to read from stream: timed out', 0, $e); + } + throw new \RuntimeException('Unable to read from stream', 0, $e); } if (false === $string) { + if ($this->timedOut()) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + throw new \RuntimeException('Unable to read from stream'); } + if ($string === '' && $this->timedOut()) { + throw new TimeoutException('Unable to read from stream: timed out'); + } + return $string; } - public function write($string): int + public function write(string $string): int { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); @@ -249,27 +248,48 @@ class Stream implements StreamInterface throw new \RuntimeException('Cannot write to a non-writable stream'); } + if ($string === '') { + return 0; + } + // We can't know the size after writing anything $this->size = null; - $result = fwrite($this->stream, $string); + + try { + $result = fwrite($this->stream, $string); + } catch (TimeoutException $e) { + throw $e; + } catch (\Exception $e) { + if ($this->writeTimedOut()) { + throw new TimeoutException('Unable to write to stream: timed out', 0, $e); + } + + throw new \RuntimeException('Unable to write to stream', 0, $e); + } if ($result === false) { + if ($this->writeTimedOut()) { + throw new TimeoutException('Unable to write to stream: timed out'); + } + throw new \RuntimeException('Unable to write to stream'); } + if ($result === 0 && $this->writeTimedOut()) { + throw new TimeoutException('Unable to write to stream: timed out'); + } + return $result; } /** - * {@inheritdoc} - * * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { if (!isset($this->stream)) { - return $key ? null : []; - } elseif (!$key) { + return $key === null ? [] : null; + } elseif ($key === null) { return $this->customMetadata + stream_get_meta_data($this->stream); } elseif (isset($this->customMetadata[$key])) { return $this->customMetadata[$key]; @@ -279,4 +299,36 @@ class Stream implements StreamInterface return $meta[$key] ?? null; } + + /** + * @see https://www.php.net/manual/en/function.fopen.php + * @see https://www.php.net/manual/en/function.gzopen.php + */ + private static function isReadableMode(string $mode): bool + { + return str_starts_with($mode, 'r') || str_contains($mode, '+'); + } + + /** + * @see https://www.php.net/manual/en/function.fopen.php + * @see https://www.php.net/manual/en/function.gzopen.php + */ + private static function isWritableMode(string $mode): bool + { + return str_starts_with($mode, 'a') + || str_starts_with($mode, 'w') + || str_starts_with($mode, 'x') + || str_starts_with($mode, 'c') + || str_contains($mode, '+'); + } + + private function timedOut(): bool + { + return StreamTimeout::isResourceReadTimedOut($this->stream); + } + + private function writeTimedOut(): bool + { + return StreamTimeout::isResourceWriteTimedOut($this->stream); + } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php index 56d4104d4..7b649a82a 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -24,33 +24,25 @@ trait StreamDecoratorTrait /** * Magic method used to create a new stream if streams are not added in * the constructor of a decorator (e.g., LazyOpenStream). - * - * @return StreamInterface */ - public function __get(string $name) + public function __get(string $name): StreamInterface { if ($name === 'stream') { $this->stream = $this->createStream(); + return $this->stream; } - throw new \UnexpectedValueException("$name not found on class"); + throw new \UnexpectedValueException(\sprintf('%s not found on class', DiagnosticValue::escape($name))); } public function __toString(): string { - try { - if ($this->isSeekable()) { - $this->seek(0); - } - return $this->getContents(); - } catch (\Throwable $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); - return ''; + if ($this->isSeekable()) { + $this->seek(0); } + + return $this->getContents(); } public function getContents(): string @@ -67,7 +59,7 @@ trait StreamDecoratorTrait { /** @var callable $callable */ $callable = [$this->stream, $method]; - $result = call_user_func_array($callable, $args); + $result = ($callable)(...$args); // Always return the wrapped object if the result is a return $this return $result === $this->stream ? $this : $result; @@ -79,11 +71,9 @@ trait StreamDecoratorTrait } /** - * {@inheritdoc} - * * @return mixed */ - public function getMetadata($key = null) + public function getMetadata(?string $key = null) { return $this->stream->getMetadata($key); } @@ -128,17 +118,21 @@ trait StreamDecoratorTrait $this->seek(0); } - public function seek($offset, $whence = SEEK_SET): void + public function seek(int $offset, int $whence = SEEK_SET): void { $this->stream->seek($offset, $whence); } - public function read($length): string + public function read(int $length): string { + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + return $this->stream->read($length); } - public function write($string): int + public function write(string $string): int { return $this->stream->write($string); } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamTimeout.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamTimeout.php new file mode 100644 index 000000000..84a6b3645 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamTimeout.php @@ -0,0 +1,106 @@ +read($length); + } catch (TimeoutException $e) { + throw $e; + } catch (\RuntimeException $e) { + self::throwIfReadTimedOut($stream, $timeoutMessage, $e); + + throw $e; + } + + if ($buffer === '') { + self::throwIfReadTimedOut($stream, $timeoutMessage); + } + + return $buffer; + } + + public static function throwIfReadTimedOut( + StreamInterface $stream, + string $message, + ?\Throwable $previous = null + ): void { + if (self::isReadTimedOut($stream)) { + throw new TimeoutException($message, 0, $previous); + } + } + + public static function throwIfWriteTimedOut(StreamInterface $stream, ?\Throwable $previous = null): void + { + if (self::isWriteTimedOut($stream)) { + throw new TimeoutException('Unable to write to stream: timed out', 0, $previous); + } + } + + public static function isReadTimedOut(StreamInterface $stream): bool + { + try { + if ($stream->getMetadata('timed_out') !== true) { + return false; + } + + return !$stream->eof(); + } catch (\Throwable $e) { + return false; + } + } + + public static function isWriteTimedOut(StreamInterface $stream): bool + { + try { + return $stream->getMetadata('timed_out') === true; + } catch (\Throwable $e) { + return false; + } + } + + /** + * @param resource $resource + */ + public static function isResourceReadTimedOut($resource): bool + { + try { + /** @var array $metadata */ + $metadata = stream_get_meta_data($resource); + + return ($metadata['timed_out'] ?? false) === true && !feof($resource); + } catch (\Throwable $e) { + return false; + } + } + + /** + * @param resource $resource + */ + public static function isResourceWriteTimedOut($resource): bool + { + try { + /** @var array $metadata */ + $metadata = stream_get_meta_data($resource); + + return ($metadata['timed_out'] ?? false) === true; + } catch (\Throwable $e) { + return false; + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php index 2a9346403..2d254d225 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -16,11 +16,10 @@ final class StreamWrapper /** @var resource */ public $context; - /** @var StreamInterface */ - private $stream; + private StreamInterface $stream; /** @var string r, r+, or w */ - private $mode; + private string $mode; /** * Returns a resource representing the stream. @@ -41,10 +40,16 @@ final class StreamWrapper $mode = 'w'; } else { throw new \InvalidArgumentException('The stream must be readable, ' - . 'writable, or both.'); + .'writable, or both.'); } - return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + $resource = @fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + + if ($resource === false) { + throw new \RuntimeException('Unable to create stream resource'); + } + + return $resource; } /** @@ -55,7 +60,7 @@ final class StreamWrapper public static function createStreamContext(StreamInterface $stream) { return stream_context_create([ - 'guzzle' => ['stream' => $stream] + 'guzzle' => ['stream' => $stream], ]); } @@ -69,43 +74,75 @@ final class StreamWrapper } } - public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool { $options = stream_context_get_options($this->context); + $stream = $options['guzzle']['stream'] ?? null; - if (!isset($options['guzzle']['stream'])) { + if (!$stream instanceof StreamInterface) { return false; } $this->mode = $mode; - $this->stream = $options['guzzle']['stream']; + $this->stream = $stream; return true; } - public function stream_read(int $count): string + /** + * @return string|false + */ + public function stream_read(int $count) { - return $this->stream->read($count); + try { + return $this->stream->read($count); + } catch (\RuntimeException $e) { + return false; + } } public function stream_write(string $data): int { - return $this->stream->write($data); + try { + return $this->stream->write($data); + } catch (\RuntimeException $e) { + return -1; + } } - public function stream_tell(): int + public function stream_flush(): bool { - return $this->stream->tell(); + return true; + } + + /** + * @return int|false + */ + public function stream_tell() + { + try { + return $this->stream->tell(); + } catch (\RuntimeException $e) { + return false; + } } public function stream_eof(): bool { - return $this->stream->eof(); + try { + return $this->stream->eof(); + } catch (\RuntimeException $e) { + return true; + } } public function stream_seek(int $offset, int $whence): bool { - $this->stream->seek($offset, $whence); + try { + $this->stream->seek($offset, $whence); + } catch (\RuntimeException $e) { + return false; + } return true; } @@ -115,61 +152,103 @@ final class StreamWrapper */ public function stream_cast(int $cast_as) { - $stream = clone($this->stream); - $resource = $stream->detach(); + try { + $stream = clone $this->stream; + $resource = $stream->detach(); + } catch (\RuntimeException $e) { + return false; + } return $resource ?? false; } /** - * @return array + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * }|false */ - public function stream_stat(): array + public function stream_stat() { + try { + $size = $this->stream->getSize(); + } catch (\RuntimeException $e) { + return false; + } + + if ($size === null) { + return false; + } + static $modeMap = [ - 'r' => 33060, + 'r' => 33060, 'rb' => 33060, 'r+' => 33206, - 'w' => 33188, - 'wb' => 33188 + 'w' => 33188, + 'wb' => 33188, ]; return [ - 'dev' => 0, - 'ino' => 0, - 'mode' => $modeMap[$this->mode], - 'nlink' => 0, - 'uid' => 0, - 'gid' => 0, - 'rdev' => 0, - 'size' => $this->stream->getSize() ?: 0, - 'atime' => 0, - 'mtime' => 0, - 'ctime' => 0, + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode] ?? 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $size, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, 'blksize' => 0, - 'blocks' => 0 + 'blocks' => 0, ]; } /** - * @return array + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * } */ public function url_stat(string $path, int $flags): array { return [ - 'dev' => 0, - 'ino' => 0, - 'mode' => 0, - 'nlink' => 0, - 'uid' => 0, - 'gid' => 0, - 'rdev' => 0, - 'size' => 0, - 'atime' => 0, - 'mtime' => 0, - 'ctime' => 0, + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, 'blksize' => 0, - 'blocks' => 0 + 'blocks' => 0, ]; } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php index b1521bcf8..188bf52d3 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -11,51 +11,30 @@ use RuntimeException; class UploadedFile implements UploadedFileInterface { - private const ERRORS = [ - UPLOAD_ERR_OK, - UPLOAD_ERR_INI_SIZE, - UPLOAD_ERR_FORM_SIZE, - UPLOAD_ERR_PARTIAL, - UPLOAD_ERR_NO_FILE, - UPLOAD_ERR_NO_TMP_DIR, - UPLOAD_ERR_CANT_WRITE, - UPLOAD_ERR_EXTENSION, + private const ERROR_MAP = [ + UPLOAD_ERR_OK => 'UPLOAD_ERR_OK', + UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE', + UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE', + UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL', + UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE', + UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR', + UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE', + UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION', ]; - /** - * @var string|null - */ - private $clientFilename; + private ?string $clientFilename; - /** - * @var string|null - */ - private $clientMediaType; + private ?string $clientMediaType; - /** - * @var int - */ - private $error; + private int $error; - /** - * @var string|null - */ - private $file; + private ?string $file = null; - /** - * @var bool - */ - private $moved = false; + private bool $moved = false; - /** - * @var int|null - */ - private $size; + private ?int $size; - /** - * @var StreamInterface|null - */ - private $stream; + private ?StreamInterface $stream = null; /** * @param StreamInterface|string|resource $streamOrFile @@ -64,11 +43,11 @@ class UploadedFile implements UploadedFileInterface $streamOrFile, ?int $size, int $errorStatus, - string $clientFilename = null, - string $clientMediaType = null + ?string $clientFilename = null, + ?string $clientMediaType = null ) { $this->setError($errorStatus); - $this->size = $size; + $this->size = Integers::assertOptionalNonNegativeSize($size, 'Uploaded file size'); $this->clientFilename = $clientFilename; $this->clientMediaType = $clientMediaType; @@ -104,7 +83,7 @@ class UploadedFile implements UploadedFileInterface */ private function setError(int $error): void { - if (false === in_array($error, UploadedFile::ERRORS, true)) { + if (!isset(UploadedFile::ERROR_MAP[$error])) { throw new InvalidArgumentException( 'Invalid error status for UploadedFile' ); @@ -113,11 +92,6 @@ class UploadedFile implements UploadedFileInterface $this->error = $error; } - private function isStringNotEmpty($param): bool - { - return is_string($param) && false === empty($param); - } - /** * Return true if there is no upload error */ @@ -137,7 +111,7 @@ class UploadedFile implements UploadedFileInterface private function validateActive(): void { if (false === $this->isOk()) { - throw new RuntimeException('Cannot retrieve stream due to upload error'); + throw new RuntimeException(\sprintf('Cannot retrieve stream due to upload error (%s)', self::ERROR_MAP[$this->error])); } if ($this->isMoved()) { @@ -159,11 +133,11 @@ class UploadedFile implements UploadedFileInterface return new LazyOpenStream($file, 'r+'); } - public function moveTo($targetPath): void + public function moveTo(string $targetPath): void { $this->validateActive(); - if (false === $this->isStringNotEmpty($targetPath)) { + if ($targetPath === '') { throw new InvalidArgumentException( 'Invalid path provided for move operation; must be a non-empty string' ); @@ -174,8 +148,13 @@ class UploadedFile implements UploadedFileInterface ? rename($this->file, $targetPath) : move_uploaded_file($this->file, $targetPath); } else { + $stream = $this->getStream(); + if ($stream->isSeekable()) { + $stream->rewind(); + } + Utils::copyToStream( - $this->getStream(), + $stream, new LazyOpenStream($targetPath, 'w') ); @@ -183,9 +162,7 @@ class UploadedFile implements UploadedFileInterface } if (false === $this->moved) { - throw new RuntimeException( - sprintf('Uploaded file could not be moved to %s', $targetPath) - ); + throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', DiagnosticValue::escape($targetPath))); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php new file mode 100644 index 000000000..968e418a3 --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UploadedFileNormalizer.php @@ -0,0 +1,130 @@ + + */ +final class UploadedFileNormalizer +{ + private function __construct() + { + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @return UploadedFileTree + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalize(array $files): array + { + $normalized = []; + + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (is_array($value) && array_key_exists('tmp_name', $value)) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (is_array($value)) { + $normalized[$key] = self::normalize($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + + return $normalized; + } + + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileTree + */ + private static function createUploadedFileFromSpec(array $value) + { + self::assertFileSpec($value); + + if (is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + + return new UploadedFile( + $value['tmp_name'], + Integers::assertNonNegativeInteger($value['size'], 'Uploaded file size'), + Integers::assertNonNegativeInteger($value['error'], 'Uploaded file error'), + $value['name'] ?? null, + $value['type'] ?? null + ); + } + + private static function assertFileSpec(array $value): void + { + if (!isset($value['tmp_name'], $value['size'], $value['error'])) { + throw new InvalidArgumentException('Invalid file specification; expected keys "tmp_name", "size", and "error".'); + } + } + + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileTree + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + self::assertNestedFileSpec($files); + + $normalizedFiles = []; + + foreach (array_keys($files['tmp_name']) as $key) { + if (!array_key_exists($key, $files['size']) || !array_key_exists($key, $files['error'])) { + throw new InvalidArgumentException('Invalid nested file specification; expected "tmp_name", "size", and "error" arrays to have matching keys.'); + } + + $spec = [ + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key] ?? null, + 'type' => $files['type'][$key] ?? null, + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + private static function assertNestedFileSpec(array $files): void + { + foreach (['tmp_name', 'size', 'error'] as $key) { + if (!isset($files[$key]) || !is_array($files[$key])) { + throw new InvalidArgumentException('Invalid nested file specification; expected keys "tmp_name", "size", and "error" to be arrays.'); + } + } + + foreach (['name', 'type'] as $key) { + if (isset($files[$key]) && !is_array($files[$key])) { + throw new InvalidArgumentException(sprintf('Invalid nested file specification; expected key "%s" to be an array when present.', $key)); + } + } + } +} diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php index 09e878d3d..7ee21aa27 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Uri.php @@ -17,15 +17,14 @@ use Psr\Http\Message\UriInterface; class Uri implements UriInterface, \JsonSerializable { /** - * Absolute http and https URIs require a host per RFC 7230 Section 2.7 - * but in generic URIs the host can be empty. So for http(s) URIs - * we apply this default host when no host is given yet to form a - * valid URI. + * Absolute http and https URIs require a host per RFC 9110 Section 4.2.1 + * but in generic URIs the host can be empty. So for http(s) URIs we apply + * this default host when no host is given yet to form a valid URI. */ private const HTTP_DEFAULT_HOST = 'localhost'; private const DEFAULT_PORTS = [ - 'http' => 80, + 'http' => 80, 'https' => 443, 'ftp' => 21, 'gopher' => 70, @@ -36,132 +35,85 @@ class Uri implements UriInterface, \JsonSerializable 'imap' => 143, 'pop' => 110, 'ldap' => 389, + 'ws' => 80, + 'wss' => 443, ]; - /** - * Unreserved characters for use in a regex. - * - * @link https://tools.ietf.org/html/rfc3986#section-2.3 - */ - private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; - - /** - * Sub-delims for use in a regex. - * - * @link https://tools.ietf.org/html/rfc3986#section-2.2 - */ - private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; - private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26']; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B']; /** @var string Uri scheme. */ - private $scheme = ''; + private string $scheme = ''; /** @var string Uri user info. */ - private $userInfo = ''; + private string $userInfo = ''; /** @var string Uri host. */ - private $host = ''; + private string $host = ''; /** @var int|null Uri port. */ - private $port; + private ?int $port = null; /** @var string Uri path. */ - private $path = ''; + private string $path = ''; /** @var string Uri query string. */ - private $query = ''; + private string $query = ''; /** @var string Uri fragment. */ - private $fragment = ''; + private string $fragment = ''; - /** @var string|null String representation */ - private $composedComponents; - - public function __construct(string $uri = '') - { + public function __construct( + #[\SensitiveParameter] + string $uri = '' + ) { if ($uri !== '') { - $parts = self::parse($uri); + $parts = UriParser::parse($uri); if ($parts === false) { - throw new MalformedUriException("Unable to parse URI: $uri"); + throw new MalformedUriException(\sprintf('Unable to parse URI: %s', Utils::redactUriStringForMessage($uri))); + } + try { + $this->applyParts($parts); + } catch (MalformedUriException $e) { + throw $e; + } catch (\InvalidArgumentException $e) { + throw new MalformedUriException($e->getMessage(), 0, $e); } - $this->applyParts($parts); } } - /** - * UTF-8 aware \parse_url() replacement. - * - * The internal function produces broken output for non ASCII domain names - * (IDN) when used with locales other than "C". - * - * On the other hand, cURL understands IDN correctly only when UTF-8 locale - * is configured ("C.UTF-8", "en_US.UTF-8", etc.). - * - * @see https://bugs.php.net/bug.php?id=52923 - * @see https://www.php.net/manual/en/function.parse-url.php#114817 - * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING - * - * @return array|false - */ - private static function parse(string $url) - { - // If IPv6 - $prefix = ''; - if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { - /** @var array{0:string, 1:string, 2:string} $matches */ - $prefix = $matches[1]; - $url = $matches[2]; - } - - /** @var string */ - $encodedUrl = preg_replace_callback( - '%[^:/@?&=#]+%usD', - static function ($matches) { - return urlencode($matches[0]); - }, - $url - ); - - $result = parse_url($prefix . $encodedUrl); - - if ($result === false) { - return false; - } - - return array_map('urldecode', $result); - } public function __toString(): string { - if ($this->composedComponents === null) { - $this->composedComponents = self::composeComponents( - $this->scheme, - $this->getAuthority(), - $this->path, - $this->query, - $this->fragment - ); - } - - return $this->composedComponents; + return self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); } /** - * Composes a URI reference string from its various components. + * Composes a URI reference string from its various components according to + * RFC 3986 Section 5.3. * - * Usually this method does not need to be called manually but instead is used indirectly via - * `Psr\Http\Message\UriInterface::__toString`. + * Usually this method does not need to be called manually but instead is + * used indirectly via `Psr\Http\Message\UriInterface::__toString`. * - * PSR-7 UriInterface treats an empty component the same as a missing component as - * getQuery(), getFragment() etc. always return a string. This explains the slight - * difference to RFC 3986 Section 5.3. + * PSR-7 UriInterface treats an empty component the same as a missing + * component as `getQuery()`, `getFragment()` etc. always return a string. + * This explains the slight difference to RFC 3986 Section 5.3. * - * Another adjustment is that the authority separator is added even when the authority is missing/empty - * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with - * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But - * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to - * that format). + * Another adjustment is that the authority separator is added even when the + * authority is missing/empty for the "file" scheme. This is because PHP + * stream functions like `file_get_contents` only work with `file:///myfile` + * but not with `file:/myfile` although they are equivalent according to RFC + * 3986. But `file:///` is the more common syntax for the file scheme anyway + * (Chrome for example redirects to that format). The separator is omitted + * when such a URI has a rootless or empty path: adding it would turn the + * first path segment into the authority of the composed URI, or compose the + * string `file://`, which cannot be parsed back into a URI. * - * @link https://tools.ietf.org/html/rfc3986#section-5.3 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3 */ public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string { @@ -169,25 +121,25 @@ class Uri implements UriInterface, \JsonSerializable // weak type checks to also accept null until we can add scalar type hints if ($scheme != '') { - $uri .= $scheme . ':'; + $uri .= $scheme.':'; } - if ($authority != '' || $scheme === 'file') { - $uri .= '//' . $authority; + if ($authority != '' || ($scheme === 'file' && str_starts_with($path, '/'))) { + $uri .= '//'.$authority; } - if ($authority != '' && $path != '' && $path[0] != '/') { - $path = '/' . $path; + if ($authority != '' && $path != '' && !str_starts_with($path, '/')) { + $path = '/'.$path; } $uri .= $path; if ($query != '') { - $uri .= '?' . $query; + $uri .= '?'.$query; } if ($fragment != '') { - $uri .= '#' . $fragment; + $uri .= '#'.$fragment; } return $uri; @@ -196,8 +148,8 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI has the default port of the current scheme. * - * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used - * independently of the implementation. + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard + * port. This method can be used independently of the implementation. */ public static function isDefaultPort(UriInterface $uri): bool { @@ -208,17 +160,18 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is absolute, i.e. it has a scheme. * - * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true - * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative - * to another URI, the base URI. Relative references can be divided into several forms: - * - network-path references, e.g. '//example.com/path' - * - absolute-path references, e.g. '/path' - * - relative-path references, e.g. 'subpath' + * An instance of UriInterface can either be an absolute URI or a relative + * reference. An absolute URI has a scheme. A relative reference is used to + * express a URI relative to another URI, the base URI. Relative references + * can be divided into several forms according to RFC 3986 Section 4.2: + * - network-path references, e.g. `//example.com/path` + * - absolute-path references, e.g. `/path` + * - relative-path references, e.g. `subpath` * * @see Uri::isNetworkPathReference * @see Uri::isAbsolutePathReference * @see Uri::isRelativePathReference - * @link https://tools.ietf.org/html/rfc3986#section-4 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ public static function isAbsolute(UriInterface $uri): bool { @@ -228,9 +181,10 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is a network-path reference. * - * A relative reference that begins with two slash characters is termed an network-path reference. + * A relative reference that begins with two slash characters is termed a + * network-path reference. * - * @link https://tools.ietf.org/html/rfc3986#section-4.2 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ public static function isNetworkPathReference(UriInterface $uri): bool { @@ -238,11 +192,12 @@ class Uri implements UriInterface, \JsonSerializable } /** - * Whether the URI is a absolute-path reference. + * Whether the URI is an absolute-path reference. * - * A relative reference that begins with a single slash character is termed an absolute-path reference. + * A relative reference that begins with a single slash character is termed + * an absolute-path reference. * - * @link https://tools.ietf.org/html/rfc3986#section-4.2 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ public static function isAbsolutePathReference(UriInterface $uri): bool { @@ -255,9 +210,10 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is a relative-path reference. * - * A relative reference that does not begin with a slash character is termed a relative-path reference. + * A relative reference that does not begin with a slash character is termed + * a relative-path reference. * - * @link https://tools.ietf.org/html/rfc3986#section-4.2 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ public static function isRelativePathReference(UriInterface $uri): bool { @@ -269,23 +225,24 @@ class Uri implements UriInterface, \JsonSerializable /** * Whether the URI is a same-document reference. * - * A same-document reference refers to a URI that is, aside from its fragment - * component, identical to the base URI. When no base URI is given, only an empty - * URI reference (apart from its fragment) is considered a same-document reference. + * A same-document reference refers to a URI that is, aside from its + * fragment component, identical to the base URI. When no base URI is given, + * only an empty URI reference (apart from its fragment) is considered a + * same-document reference. * * @param UriInterface $uri The URI to check * @param UriInterface|null $base An optional base URI to compare against * - * @link https://tools.ietf.org/html/rfc3986#section-4.4 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4 */ - public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool + public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool { if ($base !== null) { $uri = UriResolver::resolve($base, $uri); return ($uri->getScheme() === $base->getScheme()) && ($uri->getAuthority() === $base->getAuthority()) - && ($uri->getPath() === $base->getPath()) + && (self::rawPath($uri) === self::rawPath($base)) && ($uri->getQuery() === $base->getQuery()); } @@ -312,10 +269,9 @@ class Uri implements UriInterface, \JsonSerializable * Creates a new URI with a specific query string value. * * Any existing query string values that exactly match the provided key are - * removed and replaced with the given key value pair. - * - * A value of null will set the query string key without a value, e.g. "key" - * instead of "key=value". + * removed and replaced with the given key value pair. A value of null will + * set the query string key without a value, e.g. "key" instead of + * "key=value". * * @param UriInterface $uri URI to use as a base. * @param string $key Key to set. @@ -331,40 +287,75 @@ class Uri implements UriInterface, \JsonSerializable } /** - * Creates a new URI with multiple specific query string values. + * Creates a new URI with multiple query string values. * - * It has the same behavior as withQueryValue() but for an associative array of key => value. + * It has the same behavior as `withQueryValue()` but for an associative + * array of key => value. * - * @param UriInterface $uri URI to use as a base. - * @param array $keyValueArray Associative array of key and values + * @param UriInterface $uri URI to use as a base. + * @param (string|null)[] $keyValueArray Associative array of key and values */ public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface { $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); foreach ($keyValueArray as $key => $value) { + self::assertStringOrNullQueryValue($value); $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); } return $uri->withQuery(implode('&', $result)); } + /** + * @param mixed $value + */ + private static function assertStringOrNullQueryValue($value): void + { + if ($value !== null && !is_string($value)) { + throw new \InvalidArgumentException(\sprintf( + 'Query string values must be a string or null, %s given.', + \get_debug_type($value) + )); + } + } + /** * Creates a URI from a hash of `parse_url` components. * - * @link http://php.net/manual/en/function.parse-url.php + * @see https://www.php.net/manual/en/function.parse-url.php * * @throws MalformedUriException If the components do not form a valid URI. */ - public static function fromParts(array $parts): UriInterface - { + public static function fromParts( + #[\SensitiveParameter] + array $parts + ): UriInterface { $uri = new self(); - $uri->applyParts($parts); - $uri->validateState(); + try { + $uri->applyParts($parts); + $uri->validateState(); + } catch (MalformedUriException $e) { + throw $e; + } catch (\InvalidArgumentException $e) { + throw new MalformedUriException($e->getMessage(), 0, $e); + } return $uri; } + /** + * @throws \InvalidArgumentException If the host is invalid. + * + * @internal + */ + public static function assertValidHost(string $host): void + { + if (!Rfc3986::isValidHost($host)) { + throw new \InvalidArgumentException(sprintf('Invalid host: %s', DiagnosticValue::escape($host))); + } + } + public function getScheme(): string { return $this->scheme; @@ -374,11 +365,11 @@ class Uri implements UriInterface, \JsonSerializable { $authority = $this->host; if ($this->userInfo !== '') { - $authority = $this->userInfo . '@' . $authority; + $authority = $this->userInfo.'@'.$authority; } if ($this->port !== null) { - $authority .= ':' . $this->port; + $authority .= ':'.$this->port; } return $authority; @@ -401,9 +392,58 @@ class Uri implements UriInterface, \JsonSerializable public function getPath(): string { + if (str_starts_with($this->path, '//')) { + return '/'.ltrim($this->path, '/'); + } + return $this->path; } + /** + * Returns the path as it appears within a URI's string form. + * + * getPath() collapses multiple leading slashes so that a path used in + * isolation cannot be mistaken for a protocol-relative URL. Whole-URI + * operations like reference resolution and normalization (RFC 3986 + * Sections 5 and 6) are defined on the URI string form, where the path + * stays verbatim, so they must read the path through this method instead. + * For direct instances of this class the path is derived from the stored + * components, including the leading slash the string form adds to a + * rootless path when an authority is present; for subclasses and other + * implementations the path is split from the string form per RFC 3986 + * Appendix B, without validating or decoding any other component. The + * scheme is only split off when the instance reports one, as a relative + * reference can begin with a segment containing a colon that the Appendix + * B expression would otherwise read as a scheme. + * + * @throws \RuntimeException If the path cannot be split from the string form. + * + * @internal + */ + public static function rawPath(UriInterface $uri): string + { + if (get_class($uri) === self::class) { + if ($uri->path !== '' && !str_starts_with($uri->path, '/') && $uri->getAuthority() !== '') { + // composeComponents() prepends a slash to a rootless path when + // an authority is present, so the string form uses this path. + return '/'.$uri->path; + } + + return $uri->path; + } + + $pattern = $uri->getScheme() === '' + ? '%^(?://[^/?#]*)?([^?#]*)%' + : '%^(?:[^:/?#]+:)?(?://[^/?#]*)?([^?#]*)%'; + $count = preg_match($pattern, (string) $uri, $matches); + + if ($count === false) { + throw new \RuntimeException('Unable to read the URI path: '.preg_last_error_msg()); + } + + return $matches[1] ?? ''; + } + public function getQuery(): string { return $this->query; @@ -414,7 +454,7 @@ class Uri implements UriInterface, \JsonSerializable return $this->fragment; } - public function withScheme($scheme): UriInterface + public function withScheme(string $scheme): UriInterface { $scheme = $this->filterScheme($scheme); @@ -424,18 +464,20 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->scheme = $scheme; - $new->composedComponents = null; $new->removeDefaultPort(); $new->validateState(); return $new; } - public function withUserInfo($user, $password = null): UriInterface - { + public function withUserInfo( + string $user, + #[\SensitiveParameter] + ?string $password = null + ): UriInterface { $info = $this->filterUserInfoComponent($user); if ($password !== null) { - $info .= ':' . $this->filterUserInfoComponent($password); + $info .= ':'.$this->filterUserInfoComponent($password); } if ($this->userInfo === $info) { @@ -444,13 +486,12 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->userInfo = $info; - $new->composedComponents = null; $new->validateState(); return $new; } - public function withHost($host): UriInterface + public function withHost(string $host): UriInterface { $host = $this->filterHost($host); @@ -460,13 +501,12 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->host = $host; - $new->composedComponents = null; $new->validateState(); return $new; } - public function withPort($port): UriInterface + public function withPort(?int $port): UriInterface { $port = $this->filterPort($port); @@ -476,14 +516,13 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->port = $port; - $new->composedComponents = null; $new->removeDefaultPort(); $new->validateState(); return $new; } - public function withPath($path): UriInterface + public function withPath(string $path): UriInterface { $path = $this->filterPath($path); @@ -493,13 +532,12 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->path = $path; - $new->composedComponents = null; $new->validateState(); return $new; } - public function withQuery($query): UriInterface + public function withQuery(string $query): UriInterface { $query = $this->filterQueryAndFragment($query); @@ -509,12 +547,11 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->query = $query; - $new->composedComponents = null; return $new; } - public function withFragment($fragment): UriInterface + public function withFragment(string $fragment): UriInterface { $fragment = $this->filterQueryAndFragment($fragment); @@ -524,7 +561,6 @@ class Uri implements UriInterface, \JsonSerializable $new = clone $this; $new->fragment = $fragment; - $new->composedComponents = null; return $new; } @@ -539,8 +575,10 @@ class Uri implements UriInterface, \JsonSerializable * * @param array $parts Array of parse_url parts to apply. */ - private function applyParts(array $parts): void - { + private function applyParts( + #[\SensitiveParameter] + array $parts + ): void { $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; @@ -551,7 +589,7 @@ class Uri implements UriInterface, \JsonSerializable ? $this->filterHost($parts['host']) : ''; $this->port = isset($parts['port']) - ? $this->filterPort($parts['port']) + ? $this->filterPortPart($parts['port']) : null; $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) @@ -563,71 +601,78 @@ class Uri implements UriInterface, \JsonSerializable ? $this->filterQueryAndFragment($parts['fragment']) : ''; if (isset($parts['pass'])) { - $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); + $this->userInfo .= ':'.$this->filterUserInfoComponent($parts['pass']); } $this->removeDefaultPort(); } /** - * @param mixed $scheme - * * @throws \InvalidArgumentException If the scheme is invalid. */ - private function filterScheme($scheme): string + private function filterScheme(string $scheme): string { - if (!is_string($scheme)) { - throw new \InvalidArgumentException('Scheme must be a string'); + $scheme = Utils::asciiToLower($scheme); + + if (!Rfc3986::isValidScheme($scheme)) { + throw new \InvalidArgumentException(sprintf('Invalid scheme: %s', DiagnosticValue::escape($scheme))); } - return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + return $scheme; } /** - * @param mixed $component - * * @throws \InvalidArgumentException If the user info is invalid. */ - private function filterUserInfoComponent($component): string - { - if (!is_string($component)) { - throw new \InvalidArgumentException('User info must be a string'); - } - - return preg_replace_callback( - '/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', - [$this, 'rawurlencodeMatchZero'], - $component + private function filterUserInfoComponent( + #[\SensitiveParameter] + string $component + ): string { + return $this->filterComponent( + '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']++|%(?!'.Rfc3986::HEX_OCTET.'))/', + $component, + 'Unable to filter URI user info' ); } /** - * @param mixed $host - * * @throws \InvalidArgumentException If the host is invalid. */ - private function filterHost($host): string + private function filterHost(string $host): string { - if (!is_string($host)) { - throw new \InvalidArgumentException('Host must be a string'); + $host = Utils::asciiToLower($host); + $filtered = \preg_replace_callback('/%'.Rfc3986::HEX_OCTET.'/', static function (array $m): string { + return Utils::asciiToUpper($m[0]); + }, $host); + if ($filtered === null) { + throw new \RuntimeException('Unable to normalize URI host percent-encoding: '.\preg_last_error_msg()); + } + self::assertValidHost($filtered); + + if (str_starts_with($filtered, '[') && !str_starts_with($filtered, '[v')) { + // assertValidHost() accepted this bracketed value with the same + // filter_var() predicate tryCanonicalizeIpv6() validates with, and + // its pure-PHP parse cannot fail on filter-accepted text, so the + // null guard is defense in depth only. + $canonical = Rfc3986::tryCanonicalizeIpv6(substr($filtered, 1, -1)); + if ($canonical !== null) { + $filtered = '['.$canonical.']'; + } } - return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + return $filtered; } /** - * @param mixed $port - * * @throws \InvalidArgumentException If the port is invalid. */ - private function filterPort($port): ?int + private function filterPort(?int $port): ?int { if ($port === null) { return null; } - $port = (int) $port; - if (0 > $port || 0xffff < $port) { + if (0 > $port || 0xFFFF < $port) { throw new \InvalidArgumentException( sprintf('Invalid port: %d. Must be between 0 and 65535', $port) ); @@ -637,7 +682,73 @@ class Uri implements UriInterface, \JsonSerializable } /** - * @param string[] $keys + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPortPart($port): ?int + { + if (\is_int($port)) { + return $this->filterPort($port); + } + + if (\is_string($port) && \ctype_digit($port)) { + // A zero port is accepted here; only Rfc9112::parsePort() rejects + // it for HTTP Host/authority parsing. + if (Rfc3986::isValidPort($port)) { + return (int) \ltrim($port, '0'); + } + + throw new \InvalidArgumentException(sprintf( + 'Invalid port: %s. Must be between 0 and 65535', + \ltrim($port, '0') + )); + } + + throw new \InvalidArgumentException(sprintf( + 'Invalid port: %s. Must be between 0 and 65535', + self::describeInvalidPort($port) + )); + } + + /** + * @param mixed $port + */ + private static function describeInvalidPort($port): string + { + if (\is_string($port)) { + return DiagnosticValue::escape($port); + } + + if (\is_int($port)) { + return (string) $port; + } + + if (\is_bool($port)) { + return $port ? 'true' : 'false'; + } + + if ($port === null) { + return 'null'; + } + + if (\is_float($port)) { + if (\is_nan($port)) { + return 'NAN'; + } + + if (\is_infinite($port)) { + return $port > 0 ? 'INF' : '-INF'; + } + + return \sprintf('%.14G', $port); + } + + return \get_debug_type($port); + } + + /** + * @param (string|int)[] $keys * * @return string[] */ @@ -649,22 +760,25 @@ class Uri implements UriInterface, \JsonSerializable return []; } - $decodedKeys = array_map('rawurldecode', $keys); + $decodedKeys = array_map(function ($k): string { + return rawurldecode((string) $k); + }, $keys); - return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + return array_filter(explode('&', $current), static function (string $part) use ($decodedKeys): bool { return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true); }); } private static function generateQueryString(string $key, ?string $value): string { - // Query string separators ("=", "&") within the key or value need to be encoded + // Query string separators ("=", "&") and literal plus signs ("+") within the + // key or value need to be encoded // (while preventing double-encoding) before setting the query string. All other // chars that need percent-encoding will be encoded by withQuery(). $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT); if ($value !== null) { - $queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + $queryString .= '='.strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); } return $queryString; @@ -680,41 +794,44 @@ class Uri implements UriInterface, \JsonSerializable /** * Filters the path of a URI * - * @param mixed $path - * * @throws \InvalidArgumentException If the path is invalid. */ - private function filterPath($path): string + private function filterPath(string $path): string { - if (!is_string($path)) { - throw new \InvalidArgumentException('Path must be a string'); - } - - return preg_replace_callback( - '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', - [$this, 'rawurlencodeMatchZero'], - $path + return $this->filterComponent( + '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?!'.Rfc3986::HEX_OCTET.'))/', + $path, + 'Unable to filter URI path' ); } /** * Filters the query string or fragment of a URI. * - * @param mixed $str - * * @throws \InvalidArgumentException If the query or fragment is invalid. */ - private function filterQueryAndFragment($str): string + private function filterQueryAndFragment(string $str): string { - if (!is_string($str)) { - throw new \InvalidArgumentException('Query and fragment must be a string'); + return $this->filterComponent( + '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?!'.Rfc3986::HEX_OCTET.'))/', + $str, + 'Unable to filter URI query or fragment' + ); + } + + private function filterComponent( + string $pattern, + #[\SensitiveParameter] + string $component, + string $context + ): string { + $filtered = preg_replace_callback($pattern, [$this, 'rawurlencodeMatchZero'], $component); + + if ($filtered === null) { + throw new \RuntimeException($context.': '.preg_last_error_msg()); } - return preg_replace_callback( - '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', - [$this, 'rawurlencodeMatchZero'], - $str - ); + return $filtered; } private function rawurlencodeMatchZero(array $match): string @@ -729,10 +846,10 @@ class Uri implements UriInterface, \JsonSerializable } if ($this->getAuthority() === '') { - if (0 === strpos($this->path, '//')) { + if (str_starts_with($this->path, '//')) { throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); } - if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) { + if ($this->scheme === '' && str_contains(explode('/', $this->path, 2)[0], ':')) { throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); } } diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php index 70c582aa0..8bd27bff0 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriComparator.php @@ -7,19 +7,33 @@ namespace GuzzleHttp\Psr7; use Psr\Http\Message\UriInterface; /** - * Provides methods to determine if a modified URL should be considered cross-origin. + * Provides methods to determine if a modified URI should be considered + * cross-origin. * * @author Graham Campbell */ final class UriComparator { /** - * Determines if a modified URL should be considered cross-origin with - * respect to an original URL. + * Determines if a modified URI should be considered cross-origin with + * respect to an original URI. + * + * Two URIs are cross-origin when their scheme, host, or effective port + * differ. Host comparison is case-insensitive, and bracketed IPv6 literals + * are canonicalized to their RFC 5952 form from any PSR-7 implementation + * before comparison, so equivalent spellings of the same address are + * same-origin. IPvFuture literals and bracketed values that cannot be + * parsed as an IPv6 address, such as those carrying zone identifiers, + * still compare as case-insensitive text. Missing ports use the default + * port for `http`, `https`, `ws`, or `wss`. Other schemes do not receive + * implicit default ports. + * + * This helper only compares URI origins. It does not implement redirect + * handling or credential policy. */ public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool { - if (\strcasecmp($original->getHost(), $modified->getHost()) !== 0) { + if (!Utils::caselessEquals(self::normalizeHost($original), self::normalizeHost($modified))) { return true; } @@ -34,7 +48,29 @@ final class UriComparator return false; } - private static function computePort(UriInterface $uri): int + private static function normalizeHost(UriInterface $uri): string + { + $host = $uri->getHost(); + if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) { + return $host; + } + + // Foreign UriInterface implementations may carry non-canonical IPv6 + // spellings; canonicalize what is unambiguously an IPv6 address so + // equivalent literals compare as same-origin, and leave IPvFuture, + // zone-identifier, and invalid text to the caseless textual + // comparison. Validation is platform-independent, so a spelling only + // some OS parsers accept, such as zero-padded dotted octets, is + // cross-origin everywhere instead of same-origin on some systems. + $canonical = Rfc3986::tryCanonicalizeIpv6(substr($host, 1, -1)); + if ($canonical === null) { + return $host; + } + + return '['.$canonical.']'; + } + + private static function computePort(UriInterface $uri): ?int { $port = $uri->getPort(); @@ -42,7 +78,15 @@ final class UriComparator return $port; } - return 'https' === $uri->getScheme() ? 443 : 80; + if (\in_array($uri->getScheme(), ['http', 'ws'], true)) { + return 80; + } + + if (\in_array($uri->getScheme(), ['https', 'wss'], true)) { + return 443; + } + + return null; } private function __construct() diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php index e12971edd..d19a13655 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -11,12 +11,13 @@ use Psr\Http\Message\UriInterface; * * @author Tobias Schultze * - * @link https://tools.ietf.org/html/rfc3986#section-6 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6 */ final class UriNormalizer { /** - * Default normalizations which only include the ones that preserve semantics. + * Default normalizations which only include the ones that preserve + * semantics. */ public const PRESERVING_NORMALIZATIONS = self::CAPITALIZE_PERCENT_ENCODING | @@ -24,10 +25,21 @@ final class UriNormalizer self::CONVERT_EMPTY_PATH | self::REMOVE_DEFAULT_HOST | self::REMOVE_DEFAULT_PORT | - self::REMOVE_DOT_SEGMENTS; + self::REMOVE_DOT_SEGMENTS | + self::CANONICALIZE_IPV6_HOST; /** - * All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized. + * All letters within a percent-encoding triplet (e.g., "%3A") are + * case-insensitive, and should be capitalized. This applies to the + * userinfo, host, path, query, and fragment components. Bracketed + * IP-literal hosts are skipped as a legacy tolerance for nonstandard values + * other implementations may carry; zone-identifier text was briefly valid + * URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The + * userinfo and host are only rewritten when the value returned by the + * implementation matches the normalized form, and a userinfo with an empty + * user segment is never rewritten. No percent-encoding normalization is + * applied to a component that contains malformed percent syntax, such as a + * `%` not followed by two hexadecimal digits. * * Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b */ @@ -36,9 +48,22 @@ final class UriNormalizer /** * Decodes percent-encoded octets of unreserved characters. * - * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), - * hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and, - * when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers. + * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A + * and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore + * (%5F), or tilde (%7E) should not be created by URI producers and, when + * found in a URI, should be decoded to their corresponding unreserved + * characters by URI normalizers. This applies to the userinfo, host, path, + * query, and fragment components. Since the host is case-insensitive and + * PSR-7 requires it to be lowercase, octets decoded in the host are + * lowercased (e.g., "%41" becomes "a"). Bracketed IP-literal hosts are + * skipped as a legacy tolerance for nonstandard values other + * implementations may carry; zone-identifier text was briefly valid URI + * syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The + * userinfo and host are only rewritten when the value returned by the + * implementation matches the normalized form, and a userinfo with an empty + * user segment is never rewritten. No percent-encoding normalization is + * applied to a component that contains malformed percent syntax, such as a + * `%` not followed by two hexadecimal digits. * * Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/ */ @@ -54,13 +79,18 @@ final class UriNormalizer /** * Removes the default host of the given URI scheme from the URI. * - * Only the "file" scheme defines the default host "localhost". - * All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` - * are equivalent according to RFC 3986. The first format is not accepted - * by PHPs stream functions and thus already normalized implicitly to the - * second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`. + * Only the "file" scheme defines the default host "localhost". All of + * `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are + * equivalent according to RFC 3986. The first format is not accepted by + * PHPs stream functions and thus already normalized implicitly to the + * second format in the Uri class. See + * `GuzzleHttp\Psr7\Uri::composeComponents`. + * + * When removing the host leaves a URI without an authority whose path + * begins with `//`, the path is serialized with a `/.` prefix. * * Example: file://localhost/myfile → file:///myfile + * Example: file://localhost//x → file:///.//x */ public const REMOVE_DEFAULT_HOST = 8; @@ -84,9 +114,10 @@ final class UriNormalizer /** * Paths which include two or more adjacent slashes are converted to one. * - * Webservers usually ignore duplicate slashes and treat those URIs equivalent. - * But in theory those URIs do not need to be equivalent. So this normalization - * may change the semantics. Encoded slashes (%2F) are not removed. + * Webservers usually ignore duplicate slashes and treat those URIs + * equivalent. But in theory those URIs do not need to be equivalent. So + * this normalization may change the semantics. Encoded slashes (%2F) are + * not removed. * * Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html */ @@ -95,31 +126,61 @@ final class UriNormalizer /** * Sort query parameters with their values in alphabetical order. * - * However, the order of parameters in a URI may be significant (this is not defined by the standard). - * So this normalization is not safe and may change the semantics of the URI. + * However, the order of parameters in a URI may be significant (this is not + * defined by the standard). So this normalization is not safe and may + * change the semantics of the URI. * * Example: ?lang=en&article=fred → ?article=fred&lang=en * - * Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the - * purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly. + * Note: The sorting is neither locale nor Unicode aware (the URI query does + * not get decoded at all) as the purpose is to be able to compare URIs in a + * reproducible way, not to have the params sorted perfectly. */ public const SORT_QUERY_PARAMETERS = 128; + /** + * Canonicalizes IPv6 hosts to their RFC 5952 form. + * + * IPv6 addresses allow leading zeros and multiple placements of the `::` + * elision, so the same address has many textual spellings. The canonical + * form is required for IPv6 literals in URIs by RFC 5952 Section 6 and + * never changes what the URI refers to. Native `Uri` instances already + * guarantee canonical output; for other implementations, the canonical + * host is requested through `withHost()` and the result is kept only when + * the returned `getHost()` exactly matches the requested spelling, + * otherwise this step leaves the URI unchanged while other selected + * normalizations still apply, and setter exceptions propagate. + * + * Example: http://[::0:0a]/ → http://[::a]/ + */ + public const CANONICALIZE_IPV6_HOST = 256; + /** * Returns a normalized URI. * - * The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. - * This methods adds additional normalizations that can be configured with the $flags parameter. + * The scheme and host component are already normalized to lowercase per + * PSR-7 UriInterface. This method adds additional normalizations that can + * be configured with the `$flags` parameter, which is a bitmask of + * normalizations to apply. * - * PSR-7 UriInterface cannot distinguish between an empty component and a missing component as - * getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are - * treated equivalent which is not necessarily true according to RFC 3986. But that difference - * is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well. + * PSR-7 UriInterface cannot distinguish between an empty component and a + * missing component as `getQuery()`, `getFragment()` etc. always return a + * string. This means the URIs `/?#` and `/` are treated equivalent which is + * not necessarily true according to RFC 3986. But that difference is highly + * uncommon in reality. So this potential normalization is implied in PSR-7 + * as well. + * + * A path the URI cannot hold, such as a `//`-leading path without an + * authority or a relative-path reference whose first segment contains a + * colon, is prefixed with `/.` or `./` respectively instead of throwing, as + * `UriResolver::resolve()` does. The percent-encoding normalizations only + * do so where they rewrote the path. For example, decoding `a%41:` yields + * `./aA:`, since `aA:` would be an absolute URI with the scheme `aa`. * * @param UriInterface $uri The URI to normalize * @param int $flags A bitmask of normalizations to apply, see constants * - * @link https://tools.ietf.org/html/rfc3986#section-6.2 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.2 */ public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface { @@ -131,13 +192,21 @@ final class UriNormalizer $uri = self::decodeUnreservedCharacters($uri); } - if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' && - ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' + && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') ) { $uri = $uri->withPath('/'); } if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + if ($uri->getUserInfo() === '' && $uri->getPort() === null) { + $path = Uri::rawPath($uri); + if (str_starts_with($path, '//')) { + // "/." keeps a "//" path unambiguous once the authority is gone + $uri = $uri->withPath('/.'.$path); + } + } + $uri = $uri->withHost(''); } @@ -145,12 +214,24 @@ final class UriNormalizer $uri = $uri->withPort(null); } - if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { - $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); - } + $removeDotSegments = ($flags & self::REMOVE_DOT_SEGMENTS) && !Uri::isRelativePathReference($uri); - if ($flags & self::REMOVE_DUPLICATE_SLASHES) { - $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + if ($removeDotSegments || $flags & self::REMOVE_DUPLICATE_SLASHES) { + $path = Uri::rawPath($uri); + + if ($removeDotSegments) { + $path = UriResolver::removeDotSegments($path); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $path = preg_replace('#//++#', '/', $path); + + if ($path === null) { + throw new \RuntimeException('Unable to remove duplicate slashes from URI path: '.preg_last_error_msg()); + } + } + + $uri = $uri->withPath(UriResolver::guardedPath($uri, $path)); } if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { @@ -159,22 +240,28 @@ final class UriNormalizer $uri = $uri->withQuery(implode('&', $queryKeyValues)); } + if ($flags & self::CANONICALIZE_IPV6_HOST) { + $uri = self::canonicalizeIpv6Host($uri); + } + return $uri; } /** * Whether two URIs can be considered equivalent. * - * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also - * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be - * resolved against the same base URI. If this is not the case, determination of equivalence or difference of - * relative references does not mean anything. + * Both URIs are normalized automatically before comparison with the given + * `$normalizations` bitmask. The method also accepts relative URI + * references and returns true when they are equivalent. This of course + * assumes they will be resolved against the same base URI. If this is not + * the case, determination of equivalence or difference of relative + * references does not mean anything. * * @param UriInterface $uri1 An URI to compare * @param UriInterface $uri2 An URI to compare * @param int $normalizations A bitmask of normalizations to apply, see constants * - * @link https://tools.ietf.org/html/rfc3986#section-6.1 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1 */ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool { @@ -183,34 +270,183 @@ final class UriNormalizer private static function capitalizePercentEncoding(UriInterface $uri): UriInterface { - $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + $regex = '/(?:%'.Rfc3986::HEX_OCTET.')++/'; - $callback = function (array $match) { - return strtoupper($match[0]); + $callback = function (array $match): string { + return Utils::asciiToUpper($match[0]); }; - return - $uri->withPath( - preg_replace_callback($regex, $callback, $uri->getPath()) - )->withQuery( - preg_replace_callback($regex, $callback, $uri->getQuery()) - ); + $uri = self::withNormalizedUserInfo($uri, $regex, $callback); + $uri = self::withNormalizedHost($uri, $regex, $callback); + + return self::withGuardedPath($uri, self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback)) + ->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback)) + ->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback)); } private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface { $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; - $callback = function (array $match) { + $callback = function (array $match): string { return rawurldecode($match[0]); }; - return - $uri->withPath( - preg_replace_callback($regex, $callback, $uri->getPath()) - )->withQuery( - preg_replace_callback($regex, $callback, $uri->getQuery()) - ); + // The host is case-insensitive and PSR-7 requires it to be lowercase, + // so decoded ALPHA octets (e.g. "%41") must land lowercase even for + // implementations whose withHost() does not normalize the case. + $hostCallback = function (array $match): string { + return Utils::asciiToLower(rawurldecode($match[0])); + }; + + $uri = self::withNormalizedUserInfo($uri, $regex, $callback); + $uri = self::withNormalizedHost($uri, $regex, $hostCallback); + + return self::withGuardedPath($uri, self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback)) + ->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback)) + ->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback)); + } + + /** + * Writes the given path only when it differs from the current one, guarded + * so the write cannot throw. + */ + private static function withGuardedPath(UriInterface $uri, string $path): UriInterface + { + if ($path === Uri::rawPath($uri)) { + return $uri; + } + + return $uri->withPath(UriResolver::guardedPath($uri, $path)); + } + + /** + * @param callable(array): string $callback + */ + private static function withNormalizedUserInfo(UriInterface $uri, string $regex, callable $callback): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (!str_contains($userInfo, '%')) { + return $uri; + } + + $normalized = self::normalizePercentEncodingInComponent($userInfo, $regex, $callback); + + if ($normalized === $userInfo) { + return $uri; + } + + // Normalization cannot create a colon: decoding is confined to + // unreserved characters and capitalization keeps octets encoded. So + // splitting on the first colon preserves the user/password boundary. + $parts = explode(':', $normalized, 2); + + // PSR-7 defines withUserInfo('') as removing the userinfo, so a + // userinfo with an empty user segment (e.g. ":pass") cannot be + // expressed through the setter and is preserved as-is instead. + if ($parts[0] === '') { + return $uri; + } + + $candidate = $uri->withUserInfo($parts[0], $parts[1] ?? null); + + // Normalization must never lose or corrupt information, so verify the + // representation the setter returned and leave the component untouched + // when the implementation cannot represent the normalized form. + if ($candidate->getUserInfo() !== $normalized) { + return $uri; + } + + return $candidate; + } + + /** + * @param callable(array): string $callback + */ + private static function withNormalizedHost(UriInterface $uri, string $regex, callable $callback): UriInterface + { + $host = $uri->getHost(); + + // Bracketed IP-literal hosts are skipped as a legacy tolerance for + // nonstandard values other implementations may carry, such as a zone + // identifier in "[fe80::1%25eth0]"; that text was briefly valid URI + // syntax under RFC 6874, which RFC 9844 obsoleted and reverted. + if (str_starts_with($host, '[') || !str_contains($host, '%')) { + return $uri; + } + + $normalized = self::normalizePercentEncodingInComponent($host, $regex, $callback); + + if ($normalized === $host) { + return $uri; + } + + $candidate = $uri->withHost($normalized); + + // Normalization must never lose or corrupt information, so verify the + // representation the setter returned and leave the component untouched + // when the implementation cannot represent the normalized form. + if ($candidate->getHost() !== $normalized) { + return $uri; + } + + return $candidate; + } + + /** + * @param callable(array): string $callback + */ + private static function normalizePercentEncodingInComponent(string $component, string $regex, callable $callback): string + { + // Decoding a valid triplet that follows a dangling "%" would complete + // the malformed sequence into a new valid triplet ("example%6%31com" + // becomes "example%61com"), turning malformed text valid and breaking + // idempotence, so a component containing malformed percent syntax is + // returned unchanged. + $malformed = preg_match('/%(?!'.Rfc3986::HEX_OCTET.')/', $component); + + if ($malformed === false) { + throw new \RuntimeException('Unable to scan URI component percent-encoding: '.preg_last_error_msg()); + } + + if ($malformed === 1) { + return $component; + } + + $normalized = preg_replace_callback($regex, $callback, $component); + + if ($normalized === null) { + throw new \RuntimeException('Unable to normalize URI component percent-encoding: '.preg_last_error_msg()); + } + + return $normalized; + } + + private static function canonicalizeIpv6Host(UriInterface $uri): UriInterface + { + $host = $uri->getHost(); + if (!str_starts_with($host, '[') || !str_ends_with($host, ']')) { + return $uri; + } + + // Foreign UriInterface implementations may carry IPvFuture literals, + // IPv6 zone identifiers, uppercase text, or invalid spellings; + // tryCanonicalizeIpv6() canonicalizes only what is unambiguously an + // IPv6 address and leaves everything else untouched. + $canonical = Rfc3986::tryCanonicalizeIpv6(substr($host, 1, -1)); + if ($canonical === null || '['.$canonical.']' === $host) { + return $uri; + } + + $candidate = $uri->withHost('['.$canonical.']'); + // Normalization must never corrupt a component, so keep the original + // host when the implementation does not retain the canonical form. + if ($candidate->getHost() !== '['.$canonical.']') { + return $uri; + } + + return $candidate; } private function __construct() diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriParser.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriParser.php new file mode 100644 index 000000000..bedcdbffa --- /dev/null +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/UriParser.php @@ -0,0 +1,145 @@ + $floor) { + array_pop($results); + } } elseif ($segment !== '.') { $results[] = $segment; } @@ -38,9 +52,9 @@ final class UriResolver $newPath = implode('/', $results); - if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) { + if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) { // Re-add the leading slash if necessary for cases like "/.." - $newPath = '/' . $newPath; + $newPath = '/'.$newPath; } elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) { // Add the trailing slash if necessary // If newPath is not empty, then $segment must be set and is the last segment from the foreach @@ -51,9 +65,58 @@ final class UriResolver } /** - * Converts the relative URI into a new URI that is resolved against the base URI. + * Returns the path, prefixed with "/." or "./" when the URI could not + * otherwise hold it. * - * @link http://tools.ietf.org/html/rfc3986#section-5.2 + * A URI without an authority cannot hold a path beginning with "//" (RFC + * 3986 Section 3.3), but removeDotSegments() can produce one. The "/." + * prefix serializes such a path unambiguously, the same way the WHATWG URL + * Standard does, and resolves back to the same path. Hostless http and + * https Uri instances gain the default localhost host when the path is + * written, so the path cannot be mistaken for an authority and the prefix + * is not added. + * + * A relative-path reference cannot begin with a segment containing a colon + * (RFC 3986 Section 4.2), as it would be mistaken for a scheme name, but + * reference resolution and percent-encoding normalization can produce one. + * The "./" prefix the RFC prescribes resolves back to the same path. + * + * @see https://url.spec.whatwg.org/#url-serializing + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + * + * @internal + */ + public static function guardedPath(UriInterface $uri, string $path): string + { + if ($uri->getAuthority() !== '') { + return $path; + } + + if (str_starts_with($path, '//')) { + if ($uri instanceof Uri && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')) { + return $path; + } + + return '/.'.$path; + } + + if ($uri->getScheme() === '' && str_contains(explode('/', $path, 2)[0], ':')) { + return './'.$path; + } + + return $path; + } + + /** + * Converts the relative URI into a new URI that is resolved against the + * base URI. + * + * When the resolved path is a relative-path reference whose first segment + * contains a colon, which would be mistaken for a scheme name (RFC 3986 + * Section 4.2), it is prefixed with `./`, e.g. `./a:b`. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2 + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 */ public static function resolve(UriInterface $base, UriInterface $rel): UriInterface { @@ -63,72 +126,76 @@ final class UriResolver } if ($rel->getScheme() != '') { - return $rel->withPath(self::removeDotSegments($rel->getPath())); + return $rel->withPath(self::guardedPath($rel, self::removeDotSegments(Uri::rawPath($rel)))); } if ($rel->getAuthority() != '') { - $targetAuthority = $rel->getAuthority(); - $targetPath = self::removeDotSegments($rel->getPath()); - $targetQuery = $rel->getQuery(); - } else { - $targetAuthority = $base->getAuthority(); - if ($rel->getPath() === '') { - $targetPath = $base->getPath(); - $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); - } else { - if ($rel->getPath()[0] === '/') { - $targetPath = $rel->getPath(); - } else { - if ($targetAuthority != '' && $base->getPath() === '') { - $targetPath = '/' . $rel->getPath(); - } else { - $lastSlashPos = strrpos($base->getPath(), '/'); - if ($lastSlashPos === false) { - $targetPath = $rel->getPath(); - } else { - $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); - } - } - } - $targetPath = self::removeDotSegments($targetPath); - $targetQuery = $rel->getQuery(); - } + return $rel + ->withScheme($base->getScheme()) + ->withPath(self::removeDotSegments(Uri::rawPath($rel))); } - return new Uri(Uri::composeComponents( - $base->getScheme(), - $targetAuthority, - $targetPath, - $targetQuery, - $rel->getFragment() - )); + $relPath = Uri::rawPath($rel); + + if ($relPath === '') { + // the base path is used as-is per RFC 3986 Section 5.2.2, so it must not be + // rewritten through a getPath()/withPath() round-trip + return $base + ->withQuery($rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery()) + ->withFragment($rel->getFragment()); + } + + if (str_starts_with($relPath, '/')) { + $targetPath = $relPath; + } else { + $basePath = Uri::rawPath($base); + if ($base->getAuthority() != '' && $basePath === '') { + $targetPath = '/'.$relPath; + } else { + $lastSlashPos = strrpos($basePath, '/'); + if ($lastSlashPos === false) { + $targetPath = $relPath; + } else { + $targetPath = substr($basePath, 0, $lastSlashPos + 1).$relPath; + } + } + } + $targetPath = self::removeDotSegments($targetPath); + + return $base + ->withPath(self::guardedPath($base, $targetPath)) + ->withQuery($rel->getQuery()) + ->withFragment($rel->getFragment()); } /** * Returns the target URI as a relative reference from the base URI. * - * This method is the counterpart to resolve(): + * This method is the counterpart to `resolve()`: * * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) * - * One use-case is to use the current request URI as base URI and then generate relative links in your documents - * to reduce the document size or offer self-contained downloadable document archives. + * One use case is to use the current request URI as the base URI and then + * generate relative links in your documents to reduce the document size or + * offer self-contained downloadable document archives. * * $base = new Uri('http://example.com/a/b/'); * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * echo UriResolver::relativize($base, new Uri('http://example.com')); // prints '//example.com'. * - * This method also accepts a target that is already relative and will try to relativize it further. Only a - * relative-path reference will be returned as-is. + * This method also accepts a target that is already relative and will try + * to relativize it further. Only a relative-path reference will be returned + * as-is. * * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well */ public static function relativize(UriInterface $base, UriInterface $target): UriInterface { - if ($target->getScheme() !== '' && - ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') + if ($target->getScheme() !== '' + && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '') ) { return $target; } @@ -144,37 +211,68 @@ final class UriResolver return $target->withScheme(''); } + // A same-authority target with an empty path can only be expressed by a + // network-path reference (RFC 3986 Section 5.2.2). + if (self::needsNetworkPathReference($base, $target)) { + return $target->withScheme(''); + } + // We must remove the path before removing the authority because if the path starts with two slashes, the URI // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also // invalid. $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); - if ($base->getPath() !== $target->getPath()) { + if (Uri::rawPath($base) !== Uri::rawPath($target)) { return $emptyPathUri->withPath(self::getRelativePath($base, $target)); } - if ($base->getQuery() === $target->getQuery()) { + if ($base->getQuery() === $target->getQuery() && ($target->getFragment() !== '' || $base->getFragment() === '')) { // Only the target fragment is left. And it must be returned even if base and target fragment are the same. return $emptyPathUri->withQuery(''); } - // If the base URI has a query but the target has none, we cannot return an empty path reference as it would - // inherit the base query component when resolving. + // If the base URI has a query or fragment that the target lacks, we cannot return an empty path + // reference as it would inherit that base component when resolving. if ($target->getQuery() === '') { - $segments = explode('/', $target->getPath()); + $segments = explode('/', Uri::rawPath($target)); /** @var string $lastSegment */ $lastSegment = end($segments); - return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + // A reference to an empty last segment must be prefixed with "./". The same applies + // to a segment with a colon character, which would be mistaken for a scheme name. + if ($lastSegment === '' || str_contains($lastSegment, ':')) { + $lastSegment = "./$lastSegment"; + } + + return $emptyPathUri->withPath($lastSegment); } return $emptyPathUri; } + /** + * Whether relativizing to $target requires a network-path reference. + * + * A same-authority target with an empty path is expressible by a shorter + * relative reference unless resolving one would inherit a base component + * the target lacks: the base path (kept by any empty-path reference), or + * the base query or fragment (inherited by the empty reference). + */ + private static function needsNetworkPathReference(UriInterface $base, UriInterface $target): bool + { + if ($target->getAuthority() === '' || Uri::rawPath($target) !== '') { + return false; + } + + return Uri::rawPath($base) !== '' + || ($base->getQuery() !== '' && $target->getQuery() === '') + || ($base->getFragment() !== '' && $target->getFragment() === '' && $base->getQuery() === $target->getQuery()); + } + private static function getRelativePath(UriInterface $base, UriInterface $target): string { - $sourceSegments = explode('/', $base->getPath()); - $targetSegments = explode('/', $target->getPath()); + $sourceSegments = explode('/', Uri::rawPath($base)); + $targetSegments = explode('/', Uri::rawPath($target)); array_pop($sourceSegments); $targetLastSegment = array_pop($targetSegments); foreach ($sourceSegments as $i => $segment) { @@ -185,15 +283,15 @@ final class UriResolver } } $targetSegments[] = $targetLastSegment; - $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments); // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. - if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + if ($relativePath === '' || str_contains(explode('/', $relativePath, 2)[0], ':')) { $relativePath = "./$relativePath"; - } elseif ('/' === $relativePath[0]) { - if ($base->getAuthority() != '' && $base->getPath() === '') { + } elseif (str_starts_with($relativePath, '/')) { + if ($base->getAuthority() != '' && Uri::rawPath($base) === '') { // In this case an extra slash is added by resolve() automatically. So we must not add one here. $relativePath = ".$relativePath"; } else { diff --git a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php index 3a4cf3946..a2e173617 100644 --- a/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php +++ b/data/web/inc/lib/vendor/guzzlehttp/psr7/src/Utils.php @@ -4,28 +4,91 @@ declare(strict_types=1); namespace GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Exception\TimeoutException; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; final class Utils { + private function __construct() + { + } + /** - * Remove the items given by the keys, case insensitively from the data. + * Converts ASCII uppercase letters in a string to lowercase. * - * @param string[] $keys + * Unlike strtolower(), which honors LC_CTYPE before PHP 8.2, the + * conversion is locale-independent and leaves every non-ASCII byte + * unchanged, as HTTP protocol elements require. + */ + public static function asciiToLower(string $string): string + { + return strtr($string, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * Converts ASCII lowercase letters in a string to uppercase. + * + * Unlike strtoupper(), which honors LC_CTYPE before PHP 8.2, the + * conversion is locale-independent and leaves every non-ASCII byte + * unchanged, as HTTP protocol elements require. + */ + public static function asciiToUpper(string $string): string + { + return strtr($string, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); + } + + /** + * Converts the first character of a string to uppercase when it is an + * ASCII lowercase letter. + * + * Unlike ucfirst(), which honors LC_CTYPE before PHP 8.2, the conversion + * is locale-independent and leaves every non-ASCII byte unchanged, as + * HTTP protocol elements require. + */ + public static function asciiUcFirst(string $string): string + { + if ($string === '') { + return ''; + } + + return self::asciiToUpper($string[0]).substr($string, 1); + } + + /** + * Checks whether the haystack contains the needle, comparing ASCII + * letters case-insensitively and without locale sensitivity. + */ + public static function caselessContains(string $haystack, string $needle): bool + { + return str_contains(self::asciiToLower($haystack), self::asciiToLower($needle)); + } + + /** + * Checks whether two strings are equal, comparing ASCII letters + * case-insensitively and without locale sensitivity. + */ + public static function caselessEquals(string $left, string $right): bool + { + return self::asciiToLower($left) === self::asciiToLower($right); + } + + /** + * Remove the items given by the keys from the data, case-insensitively. + * + * @param array $keys */ public static function caselessRemove(array $keys, array $data): array { $result = []; foreach ($keys as &$key) { - $key = strtolower($key); + $key = self::asciiToLower((string) $key); } foreach ($data as $k => $v) { - if (!is_string($k) || !in_array(strtolower($k), $keys)) { + if (!in_array(self::asciiToLower((string) $k), $keys)) { $result[$k] = $v; } } @@ -35,7 +98,21 @@ final class Utils /** * Copy the contents of a stream into another stream until the given number - * of bytes have been read. + * of bytes have been read, returning the number of bytes copied as an + * `int`. On 32-bit PHP, an unbounded copy larger than `PHP_INT_MAX` bytes + * cannot be represented by that return type. 64-bit PHP is not affected. + * + * The destination must accept writes that make positive progress. Streams + * that return 0 as a backpressure or drop signal (a `BufferStream` at its + * high water mark, or a full `DroppingStream`) will cause this method to + * throw. For full copies, use a normal writable stream such as a file or + * `php://temp` stream. + * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a source read or destination write cannot make progress. + * + * When a source read times out, the bytes copied before it remain in the + * destination. * * @param StreamInterface $source Stream to read from * @param StreamInterface $dest Stream to write to @@ -44,34 +121,74 @@ final class Utils * * @throws \RuntimeException on error. */ - public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): int { $bufferSize = 8192; + $copied = 0; if ($maxLen === -1) { while (!$source->eof()) { - if (!$dest->write($source->read($bufferSize))) { + $buf = StreamTimeout::read($source, $bufferSize, 'Unable to read from stream: timed out'); + if ($buf === '') { break; } + + self::writeAll($dest, $buf); + $copied = Integers::add($copied, strlen($buf)); } } else { $remaining = $maxLen; while ($remaining > 0 && !$source->eof()) { - $buf = $source->read(min($bufferSize, $remaining)); + $buf = StreamTimeout::read($source, min($bufferSize, $remaining), 'Unable to read from stream: timed out'); $len = strlen($buf); if (!$len) { break; } $remaining -= $len; - $dest->write($buf); + self::writeAll($dest, $buf); + $copied = Integers::add($copied, $len); } } + + return $copied; + } + + private static function writeAll(StreamInterface $dest, string $buf): void + { + $written = 0; + $len = strlen($buf); + + while ($written < $len) { + try { + $result = $dest->write(substr($buf, $written)); + } catch (TimeoutException $e) { + throw $e; + } catch (\RuntimeException $e) { + StreamTimeout::throwIfWriteTimedOut($dest, $e); + + throw $e; + } + + if ($result <= 0) { + StreamTimeout::throwIfWriteTimedOut($dest); + + throw new \RuntimeException('Unable to write to stream'); + } + + $written += $result; + } } /** * Copy the contents of a stream into a string until the given number of * bytes have been read. * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. + * + * The bytes read before a timeout are discarded, so calling this method + * again does not return them. + * * @param StreamInterface $stream Stream to read * @param int $maxLen Maximum number of bytes to read. Pass -1 * to read the entire stream. @@ -84,18 +201,19 @@ final class Utils if ($maxLen === -1) { while (!$stream->eof()) { - $buf = $stream->read(1048576); + $buf = StreamTimeout::read($stream, 1048576, 'Unable to read from stream: timed out'); if ($buf === '') { break; } $buffer .= $buf; } + return $buffer; } $len = 0; while (!$stream->eof() && $len < $maxLen) { - $buf = $stream->read($maxLen - $len); + $buf = StreamTimeout::read($stream, $maxLen - $len, 'Unable to read from stream: timed out'); if ($buf === '') { break; } @@ -109,8 +227,11 @@ final class Utils /** * Calculate a hash of a stream. * - * This method reads the entire stream to calculate a rolling hash, based - * on PHP's `hash_init` functions. + * This method reads the entire stream to calculate a rolling hash, based on + * PHP's `hash_init` functions. + * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. * * @param StreamInterface $stream Stream to calculate the hash for * @param string $algo Hash algorithm (e.g. md5, crc32, etc) @@ -128,7 +249,12 @@ final class Utils $ctx = hash_init($algo); while (!$stream->eof()) { - hash_update($ctx, $stream->read(1048576)); + $buf = StreamTimeout::read($stream, 1048576, 'Unable to calculate stream hash: timed out'); + if ($buf === '') { + break; + } + + hash_update($ctx, $buf); } $out = hash_final($ctx, $rawOutput); @@ -145,15 +271,31 @@ final class Utils * * The changes can be one of: * - method: (string) Changes the HTTP method. - * - set_headers: (array) Sets the given headers. - * - remove_headers: (array) Remove the given headers. - * - body: (mixed) Sets the given body. - * - uri: (UriInterface) Set the URI. + * - set_headers: (array) Sets the given headers. Values must be strings + * or non-empty arrays of strings. + * - remove_headers: (array) Remove the given headers. Values may be + * strings or integers. + * - body: (mixed) Sets the given body. Present non-null values are + * converted with self::streamFor(), including resources, streams, + * iterators, callable arrays, closures, invokable objects, and stringable + * objects. String inputs remain literal bodies. + * - uri: (UriInterface) Set the URI. When the URI contains a host, the + * Host header is updated from it, and combining this with an explicit + * Host entry in set_headers throws an InvalidArgumentException. Apply + * an intentional Host override separately with withHeader() afterwards. * - query: (string) Set the query string value of the URI. * - version: (string) Set the protocol version. * * @param RequestInterface $request Request to clone and modify. - * @param array $changes Changes to apply. + * @param array{ + * method?: string, + * set_headers?: array>, + * remove_headers?: array, + * body?: resource|string|StreamInterface|callable|\Iterator|\Stringable, + * uri?: UriInterface, + * query?: string, + * version?: string + * } $changes Changes to apply. */ public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface { @@ -161,24 +303,41 @@ final class Utils return $request; } + self::assertValidModifyRequestChanges($changes); + $headers = $request->getHeaders(); if (!isset($changes['uri'])) { $uri = $request->getUri(); } else { - // Remove the host header if one is on the URI - if ($host = $changes['uri']->getHost()) { + /** @var UriInterface */ + $uri = $changes['uri']; + + $host = $uri->getHost(); + if ($host !== '') { + Uri::assertValidHost($host); + + if (isset($changes['set_headers']) && is_array($changes['set_headers'])) { + foreach (array_keys($changes['set_headers']) as $header) { + if (self::asciiToLower((string) $header) === 'host') { + throw new \InvalidArgumentException( + 'Cannot modify request with both a URI containing a host and an explicit Host header.' + ); + } + } + } + $changes['set_headers']['Host'] = $host; - if ($port = $changes['uri']->getPort()) { + $port = $uri->getPort(); + if ($port !== null) { $standardPorts = ['http' => 80, 'https' => 443]; - $scheme = $changes['uri']->getScheme(); - if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { - $changes['set_headers']['Host'] .= ':' . $port; + $scheme = $uri->getScheme(); + if (!isset($standardPorts[$scheme]) || $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':'.$port; } } } - $uri = $changes['uri']; } if (!empty($changes['remove_headers'])) { @@ -194,39 +353,156 @@ final class Utils $uri = $uri->withQuery($changes['query']); } - if ($request instanceof ServerRequestInterface) { - $new = (new ServerRequest( - $changes['method'] ?? $request->getMethod(), - $uri, - $headers, - $changes['body'] ?? $request->getBody(), - $changes['version'] ?? $request->getProtocolVersion(), - $request->getServerParams() - )) - ->withParsedBody($request->getParsedBody()) - ->withQueryParams($request->getQueryParams()) - ->withCookieParams($request->getCookieParams()) - ->withUploadedFiles($request->getUploadedFiles()); - - foreach ($request->getAttributes() as $key => $value) { - $new = $new->withAttribute($key, $value); + $hasHost = false; + foreach (array_keys($headers) as $header) { + if (self::asciiToLower((string) $header) === 'host') { + $hasHost = true; + break; } - - return $new; } - return new Request( - $changes['method'] ?? $request->getMethod(), - $uri, - $headers, - $changes['body'] ?? $request->getBody(), - $changes['version'] ?? $request->getProtocolVersion() - ); + // Match Request::__construct() by adding a Host header when one is not provided. + if (!$hasHost && $uri->getHost() !== '') { + $host = $uri->getHost(); + Uri::assertValidHost($host); + + if (($port = $uri->getPort()) !== null) { + $host .= ':'.$port; + } + + $headers = ['Host' => [$host]] + $headers; + } + + $new = $request; + + if (isset($changes['method'])) { + $new = $new->withMethod($changes['method']); + } + + if (isset($changes['uri']) || isset($changes['query'])) { + $new = $new->withUri($uri, true); + } + + if ($headers !== $new->getHeaders()) { + foreach (array_keys($new->getHeaders()) as $header) { + /** @var RequestInterface */ + $new = $new->withoutHeader((string) $header); + } + + $addedHeaders = []; + foreach ($headers as $header => $value) { + $header = (string) $header; + $normalized = self::asciiToLower($header); + + if (isset($addedHeaders[$normalized])) { + /** @var RequestInterface */ + $new = $new->withAddedHeader($addedHeaders[$normalized], $value); + } else { + /** @var RequestInterface */ + $new = $new->withHeader($header, $value); + $addedHeaders[$normalized] = $header; + } + } + } + + if (isset($changes['body'])) { + /** @var RequestInterface */ + $new = $new->withBody(self::streamFor($changes['body'])); + } + + if (isset($changes['version'])) { + /** @var RequestInterface */ + $new = $new->withProtocolVersion($changes['version']); + } + + return $new; + } + + /** + * @param array $changes + */ + private static function assertValidModifyRequestChanges(array $changes): void + { + foreach (['method', 'query', 'version'] as $key) { + if (\array_key_exists($key, $changes) && !\is_string($changes[$key])) { + self::assertValidModifyRequestChange($key, 'string', $changes[$key]); + } + } + + if (\array_key_exists('uri', $changes) && !$changes['uri'] instanceof UriInterface) { + self::assertValidModifyRequestChange('uri', 'UriInterface', $changes['uri']); + } + + if (\array_key_exists('body', $changes) && $changes['body'] === null) { + self::assertValidModifyRequestChange('body', 'resource|string|StreamInterface|callable|\Iterator|\Stringable', $changes['body']); + } + + if (\array_key_exists('set_headers', $changes)) { + if (!\is_array($changes['set_headers'])) { + self::assertValidModifyRequestChange('set_headers', 'array>', $changes['set_headers']); + } else { + foreach ($changes['set_headers'] as $header => $value) { + $headerPath = \sprintf('set_headers.%s', (string) $header); + + if (\is_array($value)) { + if ($value === []) { + self::assertValidModifyRequestChange($headerPath, 'string|non-empty-array', $value); + + break; + } + + foreach ($value as $index => $item) { + if (!\is_string($item)) { + self::assertValidModifyRequestChange(\sprintf('%s.%s', $headerPath, (string) $index), 'string', $item); + + break 2; + } + } + } elseif (!\is_string($value)) { + self::assertValidModifyRequestChange($headerPath, 'string|non-empty-array', $value); + + break; + } + } + } + } + + if (!\array_key_exists('remove_headers', $changes)) { + return; + } + + if (!\is_array($changes['remove_headers'])) { + self::assertValidModifyRequestChange('remove_headers', 'array', $changes['remove_headers']); + + return; + } + + foreach ($changes['remove_headers'] as $index => $header) { + if (!\is_string($header) && !\is_int($header)) { + self::assertValidModifyRequestChange(\sprintf('remove_headers.%s', (string) $index), 'string|int', $header); + + return; + } + } + } + + /** + * @param mixed $value + */ + private static function assertValidModifyRequestChange(string $key, string $expected, $value): void + { + throw new \InvalidArgumentException(\sprintf('Utils::modifyRequest() change "%s" must be %s; %s provided.', DiagnosticValue::escape($key), $expected, \get_debug_type($value))); } /** * Read a line from the stream up to the maximum allowed buffer length. * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. + * + * The bytes read before a timeout are discarded, so calling this method + * again does not return them. + * * @param StreamInterface $stream Stream to read from * @param int|null $maxLength Maximum buffer length */ @@ -236,7 +512,7 @@ final class Utils $size = 0; while (!$stream->eof()) { - if ('' === ($byte = $stream->read(1))) { + if ('' === ($byte = StreamTimeout::read($stream, 1, 'Unable to read line from stream: timed out'))) { return $buffer; } $buffer .= $byte; @@ -249,48 +525,183 @@ final class Utils return $buffer; } + /** + * Redact the user info part of a URI. + * + * Returns the URI with the whole userinfo component replaced by "***" + * when one is present, so neither the username nor the password survives + * into logs and diagnostics. A URI without userinfo is returned + * unchanged. + */ + public static function redactUserInfo( + #[\SensitiveParameter] + UriInterface $uri + ): UriInterface { + return $uri->getUserInfo() === '' ? $uri : $uri->withUserInfo('***'); + } + + /** + * Redacts the userinfo of a raw URI string wherever it appears in a + * subject string. + * + * The needle is taken verbatim from the raw URI rather than from parsed + * components, so credentials that URI normalization would rewrite, such + * as raw control bytes or unencoded reserved characters, are still found + * in text that embeds the URI exactly as given, for example transport + * error messages. A URI without "://" is treated as authority-form: a + * host and port with optional userinfo. + * + * A URI that does not parse has no trustworthy authority boundary, so + * everything between any scheme and its last "@" is redacted as a safe-side + * fallback. + * + * @param string $subject Text that may embed the URI + * @param string $uri Raw URI whose userinfo is redacted in the text + */ + public static function redactUserInfoInString(string $subject, string $uri): string + { + if (\strpos($uri, '@') === false) { + return $subject; + } + + $schemePosition = \strpos($uri, '://'); + $remainder = $schemePosition === false ? $uri : \substr($uri, $schemePosition + 3); + + if (\parse_url($schemePosition === false ? 'http://'.$uri : $uri) === false) { + // Raw '/', '?', or '#' separators may sit inside the credentials + // of a URI that defeats parse_url(), so the redaction cannot stop + // at the apparent authority. + $atPosition = \strrpos($remainder, '@'); + + if ($atPosition === false || $atPosition === 0) { + return $subject; + } + + return \str_replace(\substr($remainder, 0, $atPosition).'@', '***@', $subject); + } + + $authority = \substr($remainder, 0, \strcspn($remainder, '/?#')); + $atPosition = \strrpos($authority, '@'); + + if ($atPosition === false || $atPosition === 0) { + // A parseable URI with '@' only past its authority, or with an + // empty userinfo, carries no credentials to redact. + return $subject; + } + + return \str_replace(\substr($authority, 0, $atPosition).'@', '***@', $subject); + } + + /** + * Formats a URI for automatic diagnostics. + * + * The whole userinfo component is replaced by "***", and the query and + * fragment are removed. The scheme, host, port, and path are preserved. + * The returned string is diagnostic-escaped and the formatter never + * throws. + */ + public static function redactUriForMessage( + #[\SensitiveParameter] + UriInterface $uri + ): string { + try { + $raw = (string) $uri; + } catch (\Throwable $e) { + return '[unavailable URI]'; + } + + try { + if ($uri->getUserInfo() !== '') { + $uri = $uri->withUserInfo('***'); + } + + return DiagnosticValue::escape((string) $uri->withQuery('')->withFragment('')); + } catch (\Throwable $e) { + return self::redactUriStringForMessage($raw); + } + } + + /** + * Formats a raw URI for automatic diagnostics, including malformed input. + * + * The whole userinfo component is replaced by "***", and the query and + * fragment are removed. The scheme, host, port, and path are preserved + * where their boundaries can be determined safely. The returned string is + * diagnostic-escaped and the formatter never throws. + */ + public static function redactUriStringForMessage( + #[\SensitiveParameter] + string $uri + ): string { + try { + $uri = self::redactUserInfoInString($uri, $uri); + + return DiagnosticValue::escape(\substr($uri, 0, \strcspn($uri, '?#'))); + } catch (\Throwable $e) { + return '[unavailable URI]'; + } + } + /** * Create a new stream based on the input type. * - * Options is an associative array that can contain the following keys: + * Options are provided as an associative array that can contain the + * following keys: * - metadata: Array of custom metadata. * - size: Size of the stream. * * This method accepts the following `$resource` types: * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. - * - `string`: Creates a stream object that uses the given string as the contents. - * - `resource`: Creates a stream object that wraps the given PHP stream resource. - * - `Iterator`: If the provided value implements `Iterator`, then a read-only - * stream object will be created that wraps the given iterable. Each time the - * stream is read from, data from the iterator will fill a buffer and will be - * continuously called until the buffer is equal to the requested read size. - * Subsequent read calls will first read from the buffer and then call `next` - * on the underlying iterator until it is exhausted. - * - `object` with `__toString()`: If the object has the `__toString()` method, - * the object will be cast to a string and then a stream will be returned that - * uses the string value. + * - `string`: Creates a stream object that uses the given string as the + * contents. + * - `resource`: Creates a stream object that wraps the given PHP stream + * resource. + * - `Iterator`: If the provided value implements `Iterator`, then a + * read-only stream object will be created that wraps the given iterable. + * Each time the stream is read from, data from the iterator will fill a + * buffer and will be continuously called until the buffer is equal to the + * requested read size. Yielded strings, integers, finite floats, + * booleans, `null`, and stringable objects are converted to string + * chunks; non-finite floats and other values throw + * `UnexpectedValueException` when the stream is read. Values that + * stringify to an empty string are skipped while the iterator advances. + * Subsequent read calls will first read from the buffer and then call + * `next` on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` + * method, the object will be cast to a string and then a stream will be + * returned that uses the string value. * - `NULL`: When `null` is passed, an empty stream object is returned. - * - `callable` When a callable is passed, a read-only stream object will be - * created that invokes the given callable. The callable is invoked with the - * number of suggested bytes to read. The callable can return any number of - * bytes, but MUST return `false` when there is no more data to return. The - * stream object that wraps the callable will invoke the callable until the - * number of requested bytes are available. Any additional bytes will be - * buffered and used in subsequent reads. + * - `callable`: When a callable array, closure, or invokable object is + * passed and no earlier resource or object rule applies, a read-only + * stream object will be created that invokes the given callable. The + * callable is invoked with the suggested number of bytes to read. The + * callable can return fewer or more bytes than requested, but MUST return + * a non-empty string to provide data and MUST return `false` or `null` + * when there is no more data to return. Any additional bytes will be + * buffered and used in subsequent reads. String inputs are always treated + * as string bodies, even when they name callable functions. * - * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data - * @param array{size?: int, metadata?: array} $options Additional options + * @param resource|string|StreamInterface|callable|\Iterator|\Stringable|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options * * @throws \InvalidArgumentException if the $resource arg is not valid. */ public static function streamFor($resource = '', array $options = []): StreamInterface { if (is_scalar($resource)) { + if (!is_string($resource)) { + throw new \InvalidArgumentException(\sprintf( + 'Cannot create a stream from %s; pass a string, resource, StreamInterface, Stringable, Iterator, callable, or null.', + \get_debug_type($resource) + )); + } + $stream = self::tryFopen('php://temp', 'r+'); if ($resource !== '') { - fwrite($stream, (string) $resource); + fwrite($stream, $resource); fseek($stream, 0); } + return new Stream($stream, $options); } @@ -308,19 +719,36 @@ final class Utils fseek($stream, 0); $resource = $stream; } + return new Stream($resource, $options); case 'object': /** @var object $resource */ if ($resource instanceof StreamInterface) { return $resource; } elseif ($resource instanceof \Iterator) { - return new PumpStream(function () use ($resource) { - if (!$resource->valid()) { - return false; + return new PumpStream(function (int $length) use ($resource) { + while ($resource->valid()) { + $result = $resource->current(); + $resource->next(); + + if (is_float($result) && !is_finite($result)) { + throw new \UnexpectedValueException('Iterator must not yield non-finite float values'); + } + + if ($result === null || is_scalar($result)) { + $data = (string) $result; + } elseif (is_object($result) && method_exists($result, '__toString')) { + $data = (string) $result; + } else { + throw new \UnexpectedValueException('Iterator must yield scalar, null, or stringable values'); + } + + if ($data !== '') { + return $data; + } } - $result = $resource->current(); - $resource->next(); - return $result; + + return false; }, $options); } elseif (method_exists($resource, '__toString')) { return self::streamFor((string) $resource, $options); @@ -334,14 +762,14 @@ final class Utils return new PumpStream($resource, $options); } - throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + throw new \InvalidArgumentException('Invalid resource type: '.\get_debug_type($resource)); } /** * Safely opens a PHP stream resource using a filename. * - * When fopen fails, PHP normally raises a warning. This function adds an - * error handler that checks for errors and throws an exception instead. + * When `fopen()` fails, PHP normally raises a warning. This function adds + * an error handler that checks for errors and throws an exception instead. * * @param string $filename File to open * @param string $mode Mode used to open the file @@ -354,12 +782,7 @@ final class Utils { $ex = null; set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { - $ex = new \RuntimeException(sprintf( - 'Unable to open "%s" using mode "%s": %s', - $filename, - $mode, - $errstr - )); + $ex = new \RuntimeException(sprintf('Unable to open %s using mode %s: %s', DiagnosticValue::escape($filename), DiagnosticValue::escape($mode), DiagnosticValue::escape($errstr))); return true; }); @@ -368,18 +791,13 @@ final class Utils /** @var resource $handle */ $handle = fopen($filename, $mode); } catch (\Throwable $e) { - $ex = new \RuntimeException(sprintf( - 'Unable to open "%s" using mode "%s": %s', - $filename, - $mode, - $e->getMessage() - ), 0, $e); + $ex = new \RuntimeException(sprintf('Unable to open %s using mode %s: %s', DiagnosticValue::escape($filename), DiagnosticValue::escape($mode), $e->getMessage()), 0, $e); } restore_error_handler(); if ($ex) { - /** @var $ex \RuntimeException */ + /** @var \RuntimeException $ex */ throw $ex; } @@ -389,10 +807,16 @@ final class Utils /** * Safely gets the contents of a given stream. * - * When stream_get_contents fails, PHP normally raises a warning. This + * When `stream_get_contents()` fails, PHP normally raises a warning. This * function adds an error handler that checks for errors and throws an * exception instead. * + * Throws `TimeoutException` when PHP-style timeout metadata can be detected + * after a stream read cannot make progress. + * + * The bytes read before a timeout are discarded, so calling this method + * again does not return them. + * * @param resource $stream * * @throws \RuntimeException if the stream cannot be read @@ -401,10 +825,7 @@ final class Utils { $ex = null; set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool { - $ex = new \RuntimeException(sprintf( - 'Unable to read stream contents: %s', - $errstr - )); + $ex = new \RuntimeException(sprintf('Unable to read stream contents: %s', DiagnosticValue::escape($errstr))); return true; }); @@ -414,19 +835,24 @@ final class Utils $contents = stream_get_contents($stream); if ($contents === false) { - $ex = new \RuntimeException('Unable to read stream contents'); + $ex = StreamTimeout::isResourceReadTimedOut($stream) + ? new TimeoutException('Unable to read stream contents: timed out') + : new \RuntimeException('Unable to read stream contents'); + } elseif (StreamTimeout::isResourceReadTimedOut($stream)) { + $ex = new TimeoutException('Unable to read stream contents: timed out'); } + } catch (TimeoutException $e) { + $ex = $e; } catch (\Throwable $e) { - $ex = new \RuntimeException(sprintf( - 'Unable to read stream contents: %s', - $e->getMessage() - ), 0, $e); + $ex = StreamTimeout::isResourceReadTimedOut($stream) + ? new TimeoutException('Unable to read stream contents: timed out', 0, $e) + : new \RuntimeException(sprintf('Unable to read stream contents: %s', $e->getMessage()), 0, $e); } restore_error_handler(); if ($ex) { - /** @var $ex \RuntimeException */ + /** @var \RuntimeException $ex */ throw $ex; } @@ -434,11 +860,11 @@ final class Utils } /** - * Returns a UriInterface for the given value. + * Returns a `UriInterface` for the given value. * - * This function accepts a string or UriInterface and returns a - * UriInterface for the given value. If the value is already a - * UriInterface, it is returned as-is. + * This function accepts a string or `UriInterface` and returns a + * `UriInterface` for the given value. If the value is already a + * `UriInterface`, it is returned as-is. * * @param string|UriInterface $uri * diff --git a/data/web/inc/lib/vendor/illuminate/collections/Arr.php b/data/web/inc/lib/vendor/illuminate/collections/Arr.php index d83cf5f73..d3e4d89e7 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/Arr.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Arr.php @@ -4,8 +4,15 @@ namespace Illuminate\Support; use ArgumentCountError; use ArrayAccess; +use Closure; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Contracts\Support\Jsonable; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; +use JsonSerializable; +use Random\Randomizer; +use Traversable; +use WeakMap; class Arr { @@ -22,6 +29,33 @@ class Arr return is_array($value) || $value instanceof ArrayAccess; } + /** + * Determine whether the given value is arrayable. + * + * @param mixed $value + * @return ($value is array + * ? true + * : ($value is \Illuminate\Contracts\Support\Arrayable + * ? true + * : ($value is \Traversable + * ? true + * : ($value is \Illuminate\Contracts\Support\Jsonable + * ? true + * : ($value is \JsonSerializable ? true : false) + * ) + * ) + * ) + * ) + */ + public static function arrayable($value) + { + return is_array($value) + || $value instanceof Arrayable + || $value instanceof Traversable + || $value instanceof Jsonable + || $value instanceof JsonSerializable; + } + /** * Add an element to an array using "dot" notation if it doesn't exist. * @@ -39,6 +73,42 @@ class Arr return $array; } + /** + * Get an array item from an array using "dot" notation. + * + * @throws \InvalidArgumentException + */ + public static function array(ArrayAccess|array $array, string|int|null $key, ?array $default = null): array + { + $value = Arr::get($array, $key, $default); + + if (! is_array($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be an array, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + + /** + * Get a boolean item from an array using "dot" notation. + * + * @throws \InvalidArgumentException + */ + public static function boolean(ArrayAccess|array $array, string|int|null $key, ?bool $default = null): bool + { + $value = Arr::get($array, $key, $default); + + if (! is_bool($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be a boolean, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Collapse an array of arrays into a single array. * @@ -51,12 +121,10 @@ class Arr foreach ($array as $values) { if ($values instanceof Collection) { - $values = $values->all(); - } elseif (! is_array($values)) { - continue; + $results[] = $values->all(); + } elseif (is_array($values)) { + $results[] = $values; } - - $results[] = $values; } return array_merge([], ...$results); @@ -65,8 +133,10 @@ class Arr /** * Cross join the given arrays, returning all possible permutations. * - * @param iterable ...$arrays - * @return array + * @template TValue + * + * @param iterable ...$arrays + * @return array> */ public static function crossJoin(...$arrays) { @@ -92,8 +162,11 @@ class Arr /** * Divide an array into two arrays. One with keys and the other with values. * - * @param array $array - * @return array + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @return array{TKey[], TValue[]} */ public static function divide($array) { @@ -105,19 +178,29 @@ class Arr * * @param iterable $array * @param string $prepend + * @param int $depth * @return array */ - public static function dot($array, $prepend = '') + public static function dot($array, $prepend = '', $depth = INF) { $results = []; - foreach ($array as $key => $value) { - if (is_array($value) && ! empty($value)) { - $results = array_merge($results, static::dot($value, $prepend.$key.'.')); - } else { - $results[$prepend.$key] = $value; + $flatten = function ($data, $prefix, $currentDepth) use (&$results, &$flatten, $depth): void { + foreach ($data as $key => $value) { + $newKey = $prefix.$key; + + if (is_array($value) && ! empty($value) && $currentDepth < $depth) { + $flatten($value, $newKey.'.', $currentDepth + 1); + } else { + $results[$newKey] = $value; + } } - } + }; + + $flatten($array, $prepend, 0); + + // Destroy self-referencing closure to avoid memory leak... + $flatten = null; return $results; } @@ -153,11 +236,28 @@ class Arr return $array; } + /** + * Get all of the given array except for a specified array of values. + * + * @param array $array + * @param mixed $values + * @param bool $strict + * @return array + */ + public static function exceptValues($array, $values, $strict = false) + { + $values = (array) $values; + + return array_filter($array, function ($value) use ($values, $strict) { + return ! in_array($value, $values, $strict); + }); + } + /** * Determine if the given key exists in the provided array. * * @param \ArrayAccess|array $array - * @param string|int $key + * @param string|int|float $key * @return bool */ public static function exists($array, $key) @@ -170,7 +270,7 @@ class Arr return $array->offsetExists($key); } - if (is_float($key)) { + if (is_float($key) || is_null($key)) { $key = (string) $key; } @@ -178,12 +278,16 @@ class Arr } /** - * Return the first element in an array passing a given truth test. + * Return the first element in an iterable passing a given truth test. * - * @param iterable $array - * @param callable|null $callback - * @param mixed $default - * @return mixed + * @template TKey + * @template TValue + * @template TFirstDefault + * + * @param iterable $array + * @param (callable(TValue, TKey): bool)|null $callback + * @param TFirstDefault|(\Closure(): TFirstDefault) $default + * @return TValue|TFirstDefault */ public static function first($array, ?callable $callback = null, $default = null) { @@ -192,6 +296,10 @@ class Arr return value($default); } + if (is_array($array)) { + return array_first($array); + } + foreach ($array as $item) { return $item; } @@ -199,27 +307,29 @@ class Arr return value($default); } - foreach ($array as $key => $value) { - if ($callback($value, $key)) { - return $value; - } - } + $array = static::from($array); - return value($default); + $key = array_find_key($array, $callback); + + return $key !== null ? $array[$key] : value($default); } /** * Return the last element in an array passing a given truth test. * - * @param array $array - * @param callable|null $callback - * @param mixed $default - * @return mixed + * @template TKey + * @template TValue + * @template TLastDefault + * + * @param iterable $array + * @param (callable(TValue, TKey): bool)|null $callback + * @param TLastDefault|(\Closure(): TLastDefault) $default + * @return TValue|TLastDefault */ public static function last($array, ?callable $callback = null, $default = null) { if (is_null($callback)) { - return empty($array) ? value($default) : end($array); + return empty($array) ? value($default) : array_last($array); } return static::first(array_reverse($array, true), $callback, $default); @@ -271,6 +381,24 @@ class Arr return $result; } + /** + * Get a float item from an array using "dot" notation. + * + * @throws \InvalidArgumentException + */ + public static function float(ArrayAccess|array $array, string|int|null $key, ?float $default = null): float + { + $value = Arr::get($array, $key, $default); + + if (! is_float($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be a float, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Remove one or many array items from a given array using "dot" notation. * @@ -315,6 +443,32 @@ class Arr } } + /** + * Get the underlying array of items from the given argument. + * + * @template TKey of array-key = array-key + * @template TValue = mixed + * + * @param array|Enumerable|Arrayable|WeakMap|Traversable|Jsonable|JsonSerializable|object $items + * @return ($items is WeakMap ? list : array) + * + * @throws \InvalidArgumentException + */ + public static function from($items) + { + return match (true) { + is_array($items) => $items, + $items instanceof Enumerable => $items->all(), + $items instanceof Arrayable => $items->toArray(), + $items instanceof WeakMap => iterator_to_array($items, false), + $items instanceof Traversable => iterator_to_array($items), + $items instanceof Jsonable => json_decode($items->toJson(), true), + $items instanceof JsonSerializable => (array) $items->jsonSerialize(), + is_object($items) => (array) $items, + default => throw new InvalidArgumentException('Items cannot be represented by a scalar value.'), + }; + } + /** * Get an item from an array using "dot" notation. * @@ -338,7 +492,7 @@ class Arr } if (! str_contains($key, '.')) { - return $array[$key] ?? value($default); + return value($default); } foreach (explode('.', $key) as $segment) { @@ -386,6 +540,30 @@ class Arr return true; } + /** + * Determine if all keys exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function hasAll($array, $keys) + { + $keys = (array) $keys; + + if (! $array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + if (! static::has($array, $key)) { + return false; + } + } + + return true; + } + /** * Determine if any of the keys exist in an array using "dot" notation. * @@ -418,13 +596,55 @@ class Arr return false; } + /** + * Determine if all items pass the given truth test. + * + * @param iterable $array + * @param (callable(mixed, array-key): bool) $callback + * @return bool + */ + public static function every($array, callable $callback) + { + return array_all($array, $callback); + } + + /** + * Determine if some items pass the given truth test. + * + * @param iterable $array + * @param (callable(mixed, array-key): bool) $callback + * @return bool + */ + public static function some($array, callable $callback) + { + return array_any($array, $callback); + } + + /** + * Get an integer item from an array using "dot" notation. + * + * @throws \InvalidArgumentException + */ + public static function integer(ArrayAccess|array $array, string|int|null $key, ?int $default = null): int + { + $value = Arr::get($array, $key, $default); + + if (! is_int($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be an integer, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Determines if an array is associative. * * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. * * @param array $array - * @return bool + * @return ($array is list ? false : true) */ public static function isAssoc(array $array) { @@ -437,7 +657,7 @@ class Arr * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between. * * @param array $array - * @return bool + * @return ($array is list ? true : false) */ public static function isList($array) { @@ -463,7 +683,7 @@ class Arr } if (count($array) === 1) { - return end($array); + return array_last($array); } $finalItem = array_pop($array); @@ -474,13 +694,13 @@ class Arr /** * Key an associative array by a field or using a callback. * - * @param array $array + * @param iterable $array * @param callable|array|string $keyBy * @return array */ public static function keyBy($array, $keyBy) { - return Collection::make($array)->keyBy($keyBy)->all(); + return (new Collection($array))->keyBy($keyBy)->all(); } /** @@ -507,6 +727,23 @@ class Arr return array_intersect_key($array, array_flip((array) $keys)); } + /** + * Get a subset of the items from the given array by value. + * + * @param array $array + * @param mixed $values + * @param bool $strict + * @return array + */ + public static function onlyValues($array, $values, $strict = false) + { + $values = (array) $values; + + return array_filter($array, function ($value) use ($values, $strict) { + return in_array($value, $values, $strict); + }); + } + /** * Select an array of values from an array. * @@ -537,8 +774,8 @@ class Arr * Pluck an array of values from an array. * * @param iterable $array - * @param string|array|int|null $value - * @param string|array|null $key + * @param string|array|int|Closure|null $value + * @param string|array|Closure|null $key * @return array */ public static function pluck($array, $value, $key = null) @@ -548,7 +785,9 @@ class Arr [$value, $key] = static::explodePluckParameters($value, $key); foreach ($array as $item) { - $itemValue = data_get($item, $value); + $itemValue = $value instanceof Closure + ? $value($item) + : data_get($item, $value); // If the key is "null", we will just append the value to the array and keep // looping. Otherwise we will key the array using the value of the key we @@ -556,7 +795,9 @@ class Arr if (is_null($key)) { $results[] = $itemValue; } else { - $itemKey = data_get($item, $key); + $itemKey = $key instanceof Closure + ? $key($item) + : data_get($item, $key); if (is_object($itemKey) && method_exists($itemKey, '__toString')) { $itemKey = (string) $itemKey; @@ -572,15 +813,15 @@ class Arr /** * Explode the "value" and "key" arguments passed to "pluck". * - * @param string|array $value - * @param string|array|null $key + * @param Closure|array|string $value + * @param string|array|Closure|null $key * @return array */ protected static function explodePluckParameters($value, $key) { $value = is_string($value) ? explode('.', $value) : $value; - $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + $key = is_null($key) || is_array($key) || $key instanceof Closure ? $key : explode('.', $key); return [$value, $key]; } @@ -634,6 +875,25 @@ class Arr return $result; } + /** + * Run a map over each nested chunk of items. + * + * @template TKey + * @template TValue + * + * @param array $array + * @param callable(mixed...): TValue $callback + * @return array + */ + public static function mapSpread(array $array, callable $callback) + { + return static::map($array, function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + /** * Push an item onto the beginning of an array. * @@ -703,24 +963,24 @@ class Arr ); } + if (empty($array) || (! is_null($number) && $number <= 0)) { + return is_null($number) ? null : []; + } + + $keys = (new Randomizer)->pickArrayKeys($array, $requested); + if (is_null($number)) { - return $array[array_rand($array)]; + return $array[$keys[0]]; } - if ((int) $number === 0) { - return []; - } - - $keys = array_rand($array, $number); - $results = []; if ($preserveKeys) { - foreach ((array) $keys as $key) { + foreach ($keys as $key) { $results[$key] = $array[$key]; } } else { - foreach ((array) $keys as $key) { + foreach ($keys as $key) { $results[] = $array[$key]; } } @@ -768,57 +1028,102 @@ class Arr return $array; } + /** + * Push an item into an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|int|null $key + * @param mixed $values + * @return array + */ + public static function push(ArrayAccess|array &$array, string|int|null $key, mixed ...$values): array + { + $target = static::array($array, $key, []); + + array_push($target, ...$values); + + return static::set($array, $key, $target); + } + /** * Shuffle the given array and return the result. * * @param array $array - * @param int|null $seed * @return array */ - public static function shuffle($array, $seed = null) + public static function shuffle($array) { - if (is_null($seed)) { - shuffle($array); - } else { - mt_srand($seed); - shuffle($array); - mt_srand(); + return (new Randomizer)->shuffleArray($array); + } + + /** + * Get the first item in the array, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param array $array + * @param (callable(mixed, array-key): array)|null $callback + * + * @throws \Illuminate\Support\ItemNotFoundException + * @throws \Illuminate\Support\MultipleItemsFoundException + */ + public static function sole($array, ?callable $callback = null) + { + if ($callback) { + $array = static::where($array, $callback); } - return $array; + $count = count($array); + + if ($count === 0) { + throw new ItemNotFoundException; + } + + if ($count > 1) { + throw new MultipleItemsFoundException($count); + } + + return static::first($array); } /** * Sort the array using the given callback or "dot" notation. * - * @param array $array - * @param callable|array|string|null $callback - * @return array + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param callable|string|null|array $callback + * @return array */ public static function sort($array, $callback = null) { - return Collection::make($array)->sortBy($callback)->all(); + return (new Collection($array))->sortBy($callback)->all(); } /** * Sort the array in descending order using the given callback or "dot" notation. * - * @param array $array - * @param callable|array|string|null $callback - * @return array + * @template TKey of array-key + * @template TValue + * + * @param iterable $array + * @param callable|string|null|array $callback + * @return array */ public static function sortDesc($array, $callback = null) { - return Collection::make($array)->sortByDesc($callback)->all(); + return (new Collection($array))->sortByDesc($callback)->all(); } /** * Recursively sort an array by keys and values. * - * @param array $array - * @param int $options + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param int-mask-of $options * @param bool $descending - * @return array + * @return array */ public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false) { @@ -830,12 +1135,12 @@ class Arr if (! array_is_list($array)) { $descending - ? krsort($array, $options) - : ksort($array, $options); + ? krsort($array, $options) + : ksort($array, $options); } else { $descending - ? rsort($array, $options) - : sort($array, $options); + ? rsort($array, $options) + : sort($array, $options); } return $array; @@ -844,20 +1149,41 @@ class Arr /** * Recursively sort an array by keys and values in descending order. * - * @param array $array - * @param int $options - * @return array + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param int-mask-of $options + * @return array */ public static function sortRecursiveDesc($array, $options = SORT_REGULAR) { return static::sortRecursive($array, $options, true); } + /** + * Get a string item from an array using "dot" notation. + * + * @throws \InvalidArgumentException + */ + public static function string(ArrayAccess|array $array, string|int|null $key, ?string $default = null): string + { + $value = Arr::get($array, $key, $default); + + if (! is_string($value)) { + throw new InvalidArgumentException( + sprintf('Array value for key [%s] must be a string, %s found.', $key, gettype($value)) + ); + } + + return $value; + } + /** * Conditionally compile classes from an array into a CSS class list. * - * @param array $array - * @return string + * @param array|array|string $array + * @return ($array is array ? '' : ($array is '' ? '' : ($array is array{} ? '' : non-empty-string))) */ public static function toCssClasses($array) { @@ -879,8 +1205,8 @@ class Arr /** * Conditionally compile styles from an array into a style list. * - * @param array $array - * @return string + * @param array|array|string $array + * @return ($array is array ? '' : ($array is '' ? '' : ($array is array{} ? '' : non-empty-string))) */ public static function toCssStyles($array) { @@ -902,15 +1228,59 @@ class Arr /** * Filter the array using the given callback. * - * @param array $array - * @param callable $callback - * @return array + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param callable(TValue, TKey): bool $callback + * @return array */ public static function where($array, callable $callback) { return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); } + /** + * Filter the array using the negation of the given callback. + * + * @template TKey of array-key + * @template TValue + * + * @param array $array + * @param callable(TValue, TKey): bool $callback + * @return array + */ + public static function reject($array, callable $callback) + { + return static::where($array, fn ($value, $key) => ! $callback($value, $key)); + } + + /** + * Partition the array into two arrays using the given callback. + * + * @template TKey of array-key + * @template TValue of mixed + * + * @param iterable $array + * @param callable(TValue, TKey): bool $callback + * @return array, array> + */ + public static function partition($array, callable $callback) + { + $passed = []; + $failed = []; + + foreach ($array as $key => $item) { + if ($callback($item, $key)) { + $passed[$key] = $item; + } else { + $failed[$key] = $item; + } + } + + return [$passed, $failed]; + } + /** * Filter items where the value is not null. * @@ -925,8 +1295,11 @@ class Arr /** * If the given value is not an array and not null, wrap it in one. * - * @param mixed $value - * @return array + * @template TKey of array-key = array-key + * @template TValue + * + * @param array|TValue|null $value + * @return ($value is null ? array{} : ($value is array ? array : array{TValue})) */ public static function wrap($value) { diff --git a/data/web/inc/lib/vendor/illuminate/collections/Collection.php b/data/web/inc/lib/vendor/illuminate/collections/Collection.php index 2f0c768f1..1d42b2c89 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/Collection.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Collection.php @@ -7,6 +7,7 @@ use ArrayIterator; use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString; use Illuminate\Support\Traits\EnumeratesValues; use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\TransformsToResourceCollection; use InvalidArgumentException; use stdClass; use Traversable; @@ -24,7 +25,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * @use \Illuminate\Support\Traits\EnumeratesValues */ - use EnumeratesValues, Macroable; + use EnumeratesValues, Macroable, TransformsToResourceCollection; /** * The items contained in the collection. @@ -37,7 +38,6 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Create a new collection. * * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $items - * @return void */ public function __construct($items = []) { @@ -49,11 +49,12 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @param int $from * @param int $to + * @param int $step * @return static */ - public static function range($from, $to) + public static function range($from, $to, $step = 1) { - return new static(range($from, $to)); + return new static(range($from, $to, $step)); } /** @@ -76,25 +77,6 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return new LazyCollection($this->items); } - /** - * Get the average value of a given key. - * - * @param (callable(TValue): float|int)|string|null $callback - * @return float|int|null - */ - public function avg($callback = null) - { - $callback = $this->valueRetriever($callback); - - $items = $this - ->map(fn ($value) => $callback($value)) - ->filter(fn ($value) => ! is_null($value)); - - if ($count = $items->count()) { - return $items->sum() / $count; - } - } - /** * Get the median of a given key. * @@ -104,7 +86,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl public function median($key = null) { $values = (isset($key) ? $this->pluck($key) : $this) - ->filter(fn ($item) => ! is_null($item)) + ->reject(fn ($item) => is_null($item)) ->sort()->values(); $count = $values->count(); @@ -113,7 +95,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return; } - $middle = (int) ($count / 2); + $middle = intdiv($count, 2); if ($count % 2) { return $values->get($middle); @@ -160,6 +142,36 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return new static(Arr::collapse($this->items)); } + /** + * Collapse the collection of items into a single array while preserving its keys. + * + * @return static + */ + public function collapseWithKeys() + { + if (! $this->items) { + return new static; + } + + $results = []; + + foreach ($this->items as $key => $values) { + if ($values instanceof Collection) { + $values = $values->all(); + } elseif (! is_array($values)) { + continue; + } + + $results[$key] = $values; + } + + if (! $results) { + return new static; + } + + return new static(array_replace(...$results)); + } + /** * Determine if an item exists in the collection. * @@ -172,9 +184,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl { if (func_num_args() === 1) { if ($this->useAsCallable($key)) { - $placeholder = new stdClass; - - return $this->first($key, $placeholder) !== $placeholder; + return array_any($this->items, $key); } return in_array($key, $this->items); @@ -216,6 +226,19 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return ! $this->contains(...func_get_args()); } + /** + * Determine if an item is not contained in the enumerable, using strict comparison. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContainStrict($key, $operator = null, $value = null) + { + return ! $this->containsStrict(...func_get_args()); + } + /** * Cross join with the given lists, returning all possible permutations. * @@ -228,7 +251,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl public function crossJoin(...$lists) { return new static(Arr::crossJoin( - $this->items, ...array_map([$this, 'getArrayableItems'], $lists) + $this->items, ...array_map($this->getArrayableItems(...), $lists) )); } @@ -281,7 +304,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection whose keys are not present in the given items. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffKeys($items) @@ -292,7 +315,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the items in the collection whose keys are not present in the given items, using the callback. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -304,7 +327,9 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Retrieve duplicate items from the collection. * - * @param (callable(TValue): bool)|string|null $callback + * @template TMapValue + * + * @param (callable(TValue): TMapValue)|string|null $callback * @param bool $strict * @return static */ @@ -332,7 +357,9 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Retrieve duplicate items from the collection using strict comparison. * - * @param (callable(TValue): bool)|string|null $callback + * @template TMapValue + * + * @param (callable(TValue): TMapValue)|string|null $callback * @return static */ public function duplicatesStrict($callback = null) @@ -429,8 +456,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Remove an item from the collection by key. * - * \Illuminate\Contracts\Support\Arrayable|iterable|TKey $keys - * + * @param \Illuminate\Contracts\Support\Arrayable|iterable|TKey $keys * @return $this */ public function forget($keys) @@ -447,12 +473,14 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @template TGetDefault * - * @param TKey $key + * @param TKey|null $key * @param TGetDefault|(\Closure(): TGetDefault) $default * @return TValue|TGetDefault */ public function get($key, $default = null) { + $key ??= ''; + if (array_key_exists($key, $this->items)) { return $this->items[$key]; } @@ -471,8 +499,8 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl */ public function getOrPut($key, $value) { - if (array_key_exists($key, $this->items)) { - return $this->items[$key]; + if (array_key_exists($key ?? '', $this->items)) { + return $this->items[$key ?? '']; } $this->offsetSet($key, $value = value($value)); @@ -483,9 +511,16 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key|\UnitEnum|\Stringable + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static< + * ($groupBy is (array|string) + * ? array-key + * : (TGroupKey is \UnitEnum ? array-key : (TGroupKey is \Stringable ? string : TGroupKey))), + * static<($preserveKeys is true ? TKey : int), ($groupBy is array ? mixed : TValue)> + * > */ public function groupBy($groupBy, $preserveKeys = false) { @@ -509,8 +544,8 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl foreach ($groupKeys as $groupKey) { $groupKey = match (true) { is_bool($groupKey) => (int) $groupKey, - $groupKey instanceof \BackedEnum => $groupKey->value, - $groupKey instanceof \Stringable => (string) $groupKey, + $groupKey instanceof \UnitEnum => enum_value($groupKey), + $groupKey instanceof \Stringable, is_null($groupKey) => (string) $groupKey, default => $groupKey, }; @@ -534,8 +569,10 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key|\UnitEnum + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static<($keyBy is (array|string) ? array-key : (TNewKey is \UnitEnum ? array-key : TNewKey)), TValue> */ public function keyBy($keyBy) { @@ -546,6 +583,10 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl foreach ($this->items as $key => $item) { $resolvedKey = $keyBy($item, $key); + if ($resolvedKey instanceof \UnitEnum) { + $resolvedKey = enum_value($resolvedKey); + } + if (is_object($resolvedKey)) { $resolvedKey = (string) $resolvedKey; } @@ -566,19 +607,13 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl { $keys = is_array($key) ? $key : func_get_args(); - foreach ($keys as $value) { - if (! array_key_exists($value, $this->items)) { - return false; - } - } - - return true; + return array_all($keys, fn ($key) => array_key_exists($key ?? '', $this->items)); } /** * Determine if any of the keys exist in the collection. * - * @param mixed $key + * @param TKey|array $key * @return bool */ public function hasAny($key) @@ -589,19 +624,13 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl $keys = is_array($key) ? $key : func_get_args(); - foreach ($keys as $value) { - if ($this->has($value)) { - return true; - } - } - - return false; + return array_any($keys, fn ($key) => array_key_exists($key ?? '', $this->items)); } /** * Concatenate values of a given key as a string. * - * @param callable|string $value + * @param (callable(TValue, TKey): mixed)|string|null $value * @param string|null $glue * @return string */ @@ -669,7 +698,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Intersect the collection with the given items by key. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function intersectByKeys($items) @@ -682,6 +711,12 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Determine if the collection is empty or not. * + * @phpstan-assert-if-true null $this->first() + * @phpstan-assert-if-true null $this->last() + * + * @phpstan-assert-if-false TValue $this->first() + * @phpstan-assert-if-false TValue $this->last() + * * @return bool */ public function isEmpty() @@ -690,13 +725,29 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl } /** - * Determine if the collection contains a single item. + * Determine if the collection contains exactly one item. If a callback is provided, determine if exactly one item matches the condition. * + * @param (callable(TValue, TKey): bool)|null $callback * @return bool + * + * @deprecated 12.49.0 Use the `hasSole()` method instead. */ - public function containsOneItem() + public function containsOneItem(?callable $callback = null): bool { - return $this->count() === 1; + return $this->hasSole($callback); + } + + /** + * Determine if the collection contains multiple items. + * + * @param (callable(TValue, TKey): bool)|null $callback + * @return bool + * + * @deprecated 12.50.0 Use the `hasMany()` method instead. + */ + public function containsManyItems(?callable $callback = null): bool + { + return $this->hasMany($callback); } /** @@ -704,7 +755,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @param string $glue * @param string $finalGlue - * @return string + * @return TValue|string */ public function join($glue, $finalGlue = '') { @@ -756,8 +807,8 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Get the values of a given key. * - * @param string|int|array $value - * @param string|null $key + * @param \Closure|string|int|array|null $value + * @param \Closure|string|null $key * @return static */ public function pluck($value, $key = null) @@ -829,8 +880,10 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Merge the collection with the given items. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * @template TMergeValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static */ public function merge($items) { @@ -850,6 +903,23 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return new static(array_merge_recursive($this->items, $this->getArrayableItems($items))); } + /** + * Multiply the items in the collection by the multiplier. + * + * @param int $multiplier + * @return static + */ + public function multiply(int $multiplier) + { + $new = new static; + + for ($i = 0; $i < $multiplier; $i++) { + $new->push(...$this->items); + } + + return $new; + } + /** * Create a collection by using this collection for keys and another for its values. * @@ -879,10 +949,16 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @param int $step * @param int $offset - * @return static + * @return ($step is positive-int ? static : never) + * + * @throws \InvalidArgumentException */ public function nth($step, $offset = 0) { + if ($step < 1) { + throw new InvalidArgumentException('Step value must be at least 1.'); + } + $new = []; $position = 0; @@ -944,10 +1020,14 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Get and remove the last N items from the collection. * * @param int $count - * @return static|TValue|null + * @return ($count is 1 ? TValue|null : static) */ public function pop($count = 1) { + if ($count < 1) { + return new static; + } + if ($count === 1) { return array_pop($this->items); } @@ -961,7 +1041,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl $collectionCount = $this->count(); foreach (range(1, min($count, $collectionCount)) as $item) { - array_push($results, array_pop($this->items)); + $results[] = array_pop($this->items); } return new static($results); @@ -976,7 +1056,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl */ public function prepend($value, $key = null) { - $this->items = Arr::prepend($this->items, ...func_get_args()); + $this->items = Arr::prepend($this->items, ...(func_num_args() > 1 ? func_get_args() : [$value])); return $this; } @@ -996,6 +1076,19 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return $this; } + /** + * Prepend one or more items to the beginning of the collection. + * + * @param TValue ...$values + * @return $this + */ + public function unshift(...$values) + { + array_unshift($this->items, ...$values); + + return $this; + } + /** * Push all of the given items onto the collection. * @@ -1049,7 +1142,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * * @param (callable(self): int)|int|null $number * @param bool $preserveKeys - * @return static|TValue + * @return ($number is null ? TValue : static) * * @throws \InvalidArgumentException */ @@ -1111,20 +1204,62 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return array_search($value, $this->items, $strict); } - foreach ($this->items as $key => $item) { - if ($value($item, $key)) { - return $key; - } + return array_find_key($this->items, $value) ?? false; + } + + /** + * Get the item before the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function before($value, $strict = false) + { + $key = $this->search($value, $strict); + + if ($key === false) { + return null; } - return false; + $position = ($keys = $this->keys())->search($key); + + if ($position === 0) { + return null; + } + + return $this->get($keys->get($position - 1)); + } + + /** + * Get the item after the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function after($value, $strict = false) + { + $key = $this->search($value, $strict); + + if ($key === false) { + return null; + } + + $position = ($keys = $this->keys())->search($key); + + if ($position === $keys->count() - 1) { + return null; + } + + return $this->get($keys->get($position + 1)); } /** * Get and remove the first N items from the collection. * - * @param int $count - * @return static|TValue|null + * @param int<0, max> $count + * @return ($count is 1 ? TValue|null : static) * * @throws \InvalidArgumentException */ @@ -1151,7 +1286,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl $collectionCount = $this->count(); foreach (range(1, min($count, $collectionCount)) as $item) { - array_push($results, array_shift($this->items)); + $results[] = array_shift($this->items); } return new static($results); @@ -1160,23 +1295,30 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Shuffle the items in the collection. * - * @param int|null $seed * @return static */ - public function shuffle($seed = null) + public function shuffle() { - return new static(Arr::shuffle($this->items, $seed)); + return new static(Arr::shuffle($this->items)); } /** * Create chunks representing a "sliding window" view of the items in the collection. * - * @param int $size - * @param int $step + * @param positive-int $size + * @param positive-int $step * @return static + * + * @throws \InvalidArgumentException */ public function sliding($size = 2, $step = 1) { + if ($size < 1) { + throw new InvalidArgumentException('Size value must be at least 1.'); + } elseif ($step < 1) { + throw new InvalidArgumentException('Step value must be at least 1.'); + } + $chunks = floor(($this->count() - $size) / $step) + 1; return static::times($chunks, fn ($number) => $this->slice(($number - 1) * $step, $size)); @@ -1231,10 +1373,16 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Split a collection into a certain number of groups. * * @param int $numberOfGroups - * @return static + * @return ($numberOfGroups is positive-int ? static : never) + * + * @throws \InvalidArgumentException */ public function split($numberOfGroups) { + if ($numberOfGroups < 1) { + throw new InvalidArgumentException('Number of groups must be at least 1.'); + } + if ($this->isEmpty()) { return new static; } @@ -1268,17 +1416,23 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Split a collection into a certain number of groups, and fill the first groups completely. * * @param int $numberOfGroups - * @return static + * @return ($numberOfGroups is positive-int ? static : never) + * + * @throws \InvalidArgumentException */ public function splitIn($numberOfGroups) { - return $this->chunk(ceil($this->count() / $numberOfGroups)); + if ($numberOfGroups < 1) { + throw new InvalidArgumentException('Number of groups must be at least 1.'); + } + + return $this->chunk((int) ceil($this->count() / $numberOfGroups)); } /** * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1307,6 +1461,26 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl return $items->first(); } + /** + * Determine if the collection contains a single item, optionally matching the given criteria. + * + * @param (callable(TValue, TKey): bool)|string|null $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function hasSole($key = null, $operator = null, $value = null): bool + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->unless($filter == null) + ->filter($filter) + ->count() === 1; + } + /** * Get the first item in the collection but throw an exception if no matching items exist. * @@ -1338,9 +1512,10 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl * Chunk the collection into chunks of the given size. * * @param int $size - * @return static + * @param bool $preserveKeys + * @return ($preserveKeys is true ? static : static>) */ - public function chunk($size) + public function chunk($size, $preserveKeys = true) { if ($size <= 0) { return new static; @@ -1348,7 +1523,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl $chunks = []; - foreach (array_chunk($this->items, $size, true) as $chunk) { + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { $chunks[] = new static($chunk); } @@ -1358,8 +1533,8 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Chunk the collection into chunks with a callback. * - * @param callable(TValue, TKey, static): bool $callback - * @return static> + * @param callable(TValue, TKey, static): bool $callback + * @return static> */ public function chunkWhile(callable $callback) { @@ -1475,9 +1650,9 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl } } else { $result = match ($options) { - SORT_NUMERIC => intval($values[0]) <=> intval($values[1]), + SORT_NUMERIC => (int) $values[0] <=> (int) $values[1], SORT_STRING => strcmp($values[0], $values[1]), - SORT_NATURAL => strnatcmp($values[0], $values[1]), + SORT_NATURAL => strnatcmp((string) $values[0], (string) $values[1]), SORT_LOCALE_STRING => strcoll($values[0], $values[1]), default => $values[0] <=> $values[1], }; @@ -1616,8 +1791,12 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Transform each item in the collection using a callback. * - * @param callable(TValue, TKey): TValue $callback + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback * @return $this + * + * @phpstan-this-out static */ public function transform(callable $callback) { @@ -1629,11 +1808,12 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Flatten a multi-dimensional associative array with dots. * + * @param int $depth * @return static */ - public function dot() + public function dot($depth = INF) { - return new static(Arr::dot($this->all())); + return new static(Arr::dot($this->all(), '', $depth)); } /** @@ -1729,7 +1909,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Count the number of items in the collection. * - * @return int + * @return int<0, max> */ public function count(): int { @@ -1739,7 +1919,7 @@ class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerabl /** * Count the number of items in the collection by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|string|null $countBy + * @param (callable(TValue, TKey): (array-key|\UnitEnum))|string|null $countBy * @return static */ public function countBy($countBy = null) diff --git a/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php b/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php index 806a6923f..5078f90dc 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Enumerable.php @@ -45,9 +45,10 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * * @param int $from * @param int $to + * @param int $step * @return static */ - public static function range($from, $to); + public static function range($from, $to, $step = 1); /** * Wrap the given value in a collection if applicable. @@ -184,9 +185,10 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Dump the collection. * + * @param mixed ...$args * @return $this */ - public function dump(); + public function dump(...$args); /** * Get the items that are not present in the given items. @@ -225,7 +227,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items whose keys are not present in the given items. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ public function diffKeys($items); @@ -233,7 +235,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the items whose keys are not present in the given items, using the callback. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @param callable(TKey, TKey): int $callback * @return static */ @@ -333,7 +335,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, public function whenNotEmpty(callable $callback, ?callable $default = null); /** - * Apply the callback if the given "value" is (or resolves to) truthy. + * Apply the callback if the given "value" is (or resolves to) falsy. * * @template TUnlessReturnType * @@ -517,17 +519,21 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static<($groupBy is string ? array-key : ($groupBy is array ? array-key : TGroupKey)), static<($preserveKeys is true ? TKey : int), ($groupBy is array ? mixed : TValue)>> */ public function groupBy($groupBy, $preserveKeys = false); /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static<($keyBy is string ? array-key : ($keyBy is array ? array-key : TNewKey)), TValue> */ public function keyBy($keyBy); @@ -550,7 +556,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Concatenate values of a given key as a string. * - * @param callable|string $value + * @param (callable(TValue, TKey): mixed)|string $value * @param string|null $glue * @return string */ @@ -565,11 +571,37 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, public function intersect($items); /** - * Intersect the collection with the given items by key. + * Intersect the collection with the given items, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectUsing($items, callable $callback); + + /** + * Intersect the collection with the given items with additional index check. * * @param \Illuminate\Contracts\Support\Arrayable|iterable $items * @return static */ + public function intersectAssoc($items); + + /** + * Intersect the collection with the given items with additional index check, using the callback. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @param callable(TValue, TValue): int $callback + * @return static + */ + public function intersectAssocUsing($items, callable $callback); + + /** + * Intersect the collection with the given items by key. + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static + */ public function intersectByKeys($items); /** @@ -593,6 +625,13 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, */ public function containsOneItem(); + /** + * Determine if the collection contains multiple items. + * + * @return bool + */ + public function containsManyItems(); + /** * Join all items from the collection using a string. The final items can use a separate glue string. * @@ -701,8 +740,10 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Merge the collection with the given items. * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * @template TMergeValue + * + * @param \Illuminate\Contracts\Support\Arrayable|iterable $items + * @return static */ public function merge($items); @@ -815,7 +856,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback * @param TReduceInitial $initial - * @return TReduceReturnType + * @return TReduceInitial|TReduceReturnType */ public function reduce(callable $callback, $initial = null); @@ -862,13 +903,30 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, */ public function search($value, $strict = false); + /** + * Get the item before the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function before($value, $strict = false); + + /** + * Get the item after the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function after($value, $strict = false); + /** * Shuffle the items in the collection. * - * @param int|null $seed * @return static */ - public function shuffle($seed = null); + public function shuffle(); /** * Create chunks representing a "sliding window" view of the items in the collection. @@ -936,7 +994,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, /** * Get the first item in the collection but throw an exception if no matching items exist. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1102,7 +1160,7 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, * * @param string|array $value * @param string|null $key - * @return static + * @return static */ public function pluck($value, $key = null); @@ -1220,6 +1278,15 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, */ public function toJson($options = 0); + /** + * Get the collection of items as pretty print formatted JSON. + * + * + * @param int $options + * @return string + */ + public function toPrettyJson(int $options = 0); + /** * Get a CachingIterator instance. * diff --git a/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php b/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php index 106356c3a..035d0fda4 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php +++ b/data/web/inc/lib/vendor/illuminate/collections/HigherOrderCollectionProxy.php @@ -3,14 +3,19 @@ namespace Illuminate\Support; /** - * @mixin \Illuminate\Support\Enumerable + * @template TKey of array-key + * + * @template-covariant TValue + * + * @mixin \Illuminate\Support\Enumerable + * @mixin TValue */ class HigherOrderCollectionProxy { /** * The collection being operated on. * - * @var \Illuminate\Support\Enumerable + * @var \Illuminate\Support\Enumerable */ protected $collection; @@ -24,9 +29,8 @@ class HigherOrderCollectionProxy /** * Create a new proxy instance. * - * @param \Illuminate\Support\Enumerable $collection + * @param \Illuminate\Support\Enumerable $collection * @param string $method - * @return void */ public function __construct(Enumerable $collection, $method) { @@ -57,7 +61,9 @@ class HigherOrderCollectionProxy public function __call($method, $parameters) { return $this->collection->{$this->method}(function ($value) use ($method, $parameters) { - return $value->{$method}(...$parameters); + return is_string($value) + ? $value::{$method}(...$parameters) + : $value->{$method}(...$parameters); }); } } diff --git a/data/web/inc/lib/vendor/illuminate/collections/LazyCollection.php b/data/web/inc/lib/vendor/illuminate/collections/LazyCollection.php index cd268b73d..028ab882f 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/LazyCollection.php +++ b/data/web/inc/lib/vendor/illuminate/collections/LazyCollection.php @@ -4,6 +4,8 @@ namespace Illuminate\Support; use ArrayIterator; use Closure; +use DateInterval; +use DateTimeImmutable; use DateTimeInterface; use Generator; use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString; @@ -39,7 +41,6 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Create a new lazy collection instance. * * @param \Illuminate\Contracts\Support\Arrayable|iterable|(Closure(): \Generator)|self|array|null $source - * @return void */ public function __construct($source = null) { @@ -75,17 +76,24 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @param int $from * @param int $to - * @return static + * @param int $step + * @return ($step is zero ? never : static) + * + * @throws \InvalidArgumentException */ - public static function range($from, $to) + public static function range($from, $to, $step = 1) { - return new static(function () use ($from, $to) { + if ($step == 0) { + throw new InvalidArgumentException('Step value cannot be zero.'); + } + + return new static(function () use ($from, $to, $step) { if ($from <= $to) { - for (; $from <= $to; $from++) { + for (; $from <= $to; $from += abs($step)) { yield $from; } } else { - for (; $from >= $to; $from--) { + for (; $from >= $to; $from -= abs($step)) { yield $from; } } @@ -109,7 +117,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Eager load all items into a new lazy collection backed by an array. * - * @return static + * @return static */ public function eager() { @@ -119,7 +127,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Cache values as they're enumerated. * - * @return static + * @return static */ public function remember() { @@ -154,17 +162,6 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable }); } - /** - * Get the average value of a given key. - * - * @param (callable(TValue): float|int)|string|null $callback - * @return float|int|null - */ - public function avg($callback = null) - { - return $this->collect()->avg($callback); - } - /** * Get the median of a given key. * @@ -205,6 +202,24 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable }); } + /** + * Collapse the collection of items into a single array while preserving its keys. + * + * @return static + */ + public function collapseWithKeys() + { + return new static(function () { + foreach ($this as $values) { + if (is_array($values) || $values instanceof Enumerable) { + foreach ($values as $key => $value) { + yield $key => $value; + } + } + } + }); + } + /** * Determine if an item exists in the enumerable. * @@ -277,23 +292,31 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Cross join the given iterables, returning all possible permutations. + * Determine if an item is not contained in the enumerable, using strict comparison. * - * @template TCrossJoinKey - * @template TCrossJoinValue - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable ...$arrays - * @return static> + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool */ + public function doesntContainStrict($key, $operator = null, $value = null) + { + return ! $this->containsStrict(...func_get_args()); + } + + /** + * {@inheritDoc} + */ + #[\Override] public function crossJoin(...$arrays) { - return $this->passthru('crossJoin', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** * Count the number of items in the collection by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|string|null $countBy + * @param (callable(TValue, TKey): (array-key|\UnitEnum))|string|null $countBy * @return static */ public function countBy($countBy = null) @@ -306,7 +329,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable $counts = []; foreach ($this as $key => $value) { - $group = $countBy($value, $key); + $group = enum_value($countBy($value, $key)); if (empty($counts[$group])) { $counts[$group] = 0; @@ -320,106 +343,84 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Get the items that are not present in the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function diff($items) { - return $this->passthru('diff', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Get the items that are not present in the given items, using the callback. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @param callable(TValue, TValue): int $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function diffUsing($items, callable $callback) { - return $this->passthru('diffUsing', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Get the items whose keys and values are not present in the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function diffAssoc($items) { - return $this->passthru('diffAssoc', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Get the items whose keys and values are not present in the given items, using the callback. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @param callable(TKey, TKey): int $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function diffAssocUsing($items, callable $callback) { - return $this->passthru('diffAssocUsing', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Get the items whose keys are not present in the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function diffKeys($items) { - return $this->passthru('diffKeys', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Get the items whose keys are not present in the given items, using the callback. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @param callable(TKey, TKey): int $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function diffKeysUsing($items, callable $callback) { - return $this->passthru('diffKeysUsing', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Retrieve duplicate items. - * - * @param (callable(TValue): bool)|string|null $callback - * @param bool $strict - * @return static + * {@inheritDoc} */ + #[\Override] public function duplicates($callback = null, $strict = false) { - return $this->passthru('duplicates', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Retrieve duplicate items using strict comparison. - * - * @param (callable(TValue): bool)|string|null $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function duplicatesStrict($callback = null) { - return $this->passthru('duplicatesStrict', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Get all items except for those with the specified keys. - * - * @param \Illuminate\Support\Enumerable|array $keys - * @return static + * {@inheritDoc} */ + #[\Override] public function except($keys) { - return $this->passthru('except', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** @@ -535,22 +536,31 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Group an associative array by a field or using a callback. + * {@inheritDoc} * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy - * @param bool $preserveKeys - * @return static> + * @template TGroupKey of array-key|\UnitEnum|\Stringable + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy + * @return static< + * ($groupBy is (array|string) + * ? array-key + * : (TGroupKey is \UnitEnum ? array-key : (TGroupKey is \Stringable ? string : TGroupKey))), + * static<($preserveKeys is true ? TKey : int), ($groupBy is array ? mixed : TValue)> + * > */ + #[\Override] public function groupBy($groupBy, $preserveKeys = false) { - return $this->passthru('groupBy', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key|\UnitEnum + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static<($keyBy is (array|string) ? array-key : (TNewKey is \UnitEnum ? array-key : TNewKey)), TValue> */ public function keyBy($keyBy) { @@ -611,7 +621,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Concatenate values of a given key as a string. * - * @param callable|string $value + * @param (callable(TValue, TKey): mixed)|string $value * @param string|null $glue * @return string */ @@ -621,60 +631,48 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Intersect the collection with the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function intersect($items) { - return $this->passthru('intersect', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Intersect the collection with the given items, using the callback. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @param callable(TValue, TValue): int $callback - * @return static + * {@inheritDoc} */ - public function intersectUsing() + #[\Override] + public function intersectUsing($items, callable $callback) { - return $this->passthru('intersectUsing', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Intersect the collection with the given items with additional index check. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function intersectAssoc($items) { - return $this->passthru('intersectAssoc', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Intersect the collection with the given items with additional index check, using the callback. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @param callable(TValue, TValue): int $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function intersectAssocUsing($items, callable $callback) { - return $this->passthru('intersectAssocUsing', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Intersect the collection with the given items by key. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function intersectByKeys($items) { - return $this->passthru('intersectByKeys', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** @@ -690,11 +688,26 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable /** * Determine if the collection contains a single item. * + * @param (callable(TValue, TKey): bool)|null $callback * @return bool + * + * @deprecated 12.49.0 Use the `hasSole()` method instead. */ - public function containsOneItem() + public function containsOneItem(?callable $callback = null): bool { - return $this->take(2)->count() === 1; + return $this->hasSole($callback); + } + + /** + * Determine if the collection contains multiple items. + * + * @return bool + * + * @deprecated 12.50.0 Use the `hasMany()` method instead. + */ + public function containsManyItems(): bool + { + return $this->hasMany(); } /** @@ -750,7 +763,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @param string|array $value * @param string|null $key - * @return static + * @return static */ public function pluck($value, $key = null) { @@ -758,12 +771,16 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable [$value, $key] = $this->explodePluckParameters($value, $key); foreach ($this as $item) { - $itemValue = data_get($item, $value); + $itemValue = $value instanceof Closure + ? $value($item) + : data_get($item, $value); if (is_null($key)) { yield $itemValue; } else { - $itemKey = data_get($item, $key); + $itemKey = $key instanceof Closure + ? $key($item) + : data_get($item, $key); if (is_object($itemKey) && method_exists($itemKey, '__toString')) { $itemKey = (string) $itemKey; @@ -793,19 +810,12 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Run a dictionary map over the items. - * - * The callback should return an associative array with a single key/value pair. - * - * @template TMapToDictionaryKey of array-key - * @template TMapToDictionaryValue - * - * @param callable(TValue, TKey): array $callback - * @return static> + * {@inheritDoc} */ + #[\Override] public function mapToDictionary(callable $callback) { - return $this->passthru('mapToDictionary', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** @@ -829,27 +839,32 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Merge the collection with the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function merge($items) { - return $this->passthru('merge', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Recursively merge the collection with the given items. - * - * @template TMergeRecursiveValue - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function mergeRecursive($items) { - return $this->passthru('mergeRecursive', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); + } + + /** + * Multiply the items in the collection by the multiplier. + * + * @param int $multiplier + * @return static + */ + public function multiply(int $multiplier) + { + return $this->passthru(__FUNCTION__, func_get_args()); } /** @@ -886,14 +901,12 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Union the collection with the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function union($items) { - return $this->passthru('union', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** @@ -901,10 +914,16 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @param int $step * @param int $offset - * @return static + * @return ($step is positive-int ? static : never) + * + * @throws \InvalidArgumentException */ public function nth($step, $offset = 0) { + if ($step < 1) { + throw new InvalidArgumentException('Step value must be at least 1.'); + } + return new static(function () use ($step, $offset) { $position = 0; @@ -1009,11 +1028,12 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Get one or a specified number of items randomly from the collection. * * @param int|null $number + * @param bool $preserveKeys * @return static|TValue * * @throws \InvalidArgumentException */ - public function random($number = null) + public function random($number = null, $preserveKeys = false) { $result = $this->collect()->random(...func_get_args()); @@ -1048,24 +1068,21 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Recursively replace the collection items with the given items. - * - * @param \Illuminate\Contracts\Support\Arrayable|iterable $items - * @return static + * {@inheritDoc} */ + #[\Override] public function replaceRecursive($items) { - return $this->passthru('replaceRecursive', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Reverse items order. - * - * @return static + * {@inheritDoc} */ + #[\Override] public function reverse() { - return $this->passthru('reverse', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** @@ -1094,25 +1111,91 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Shuffle the items in the collection. + * Get the item before the given item. * - * @param int|null $seed - * @return static + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null */ - public function shuffle($seed = null) + public function before($value, $strict = false) { - return $this->passthru('shuffle', func_get_args()); + $previous = null; + + /** @var (callable(TValue,TKey): bool) $predicate */ + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($predicate($item, $key)) { + return $previous; + } + + $previous = $item; + } + + return null; + } + + /** + * Get the item after the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function after($value, $strict = false) + { + $found = false; + + /** @var (callable(TValue,TKey): bool) $predicate */ + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($found) { + return $item; + } + + if ($predicate($item, $key)) { + $found = true; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + #[\Override] + public function shuffle() + { + return $this->passthru(__FUNCTION__, []); } /** * Create chunks representing a "sliding window" view of the items in the collection. * - * @param int $size - * @param int $step + * @param positive-int $size + * @param positive-int $step * @return static + * + * @throws \InvalidArgumentException */ public function sliding($size = 2, $step = 1) { + if ($size < 1) { + throw new InvalidArgumentException('Size value must be at least 1.'); + } elseif ($step < 1) { + throw new InvalidArgumentException('Step value must be at least 1.'); + } + return new static(function () use ($size, $step) { $iterator = $this->getIterator(); @@ -1205,16 +1288,13 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Get a slice of items from the enumerable. - * - * @param int $offset - * @param int|null $length - * @return static + * {@inheritDoc} */ + #[\Override] public function slice($offset, $length = null) { if ($offset < 0 || $length < 0) { - return $this->passthru('slice', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } $instance = $this->skip($offset); @@ -1223,20 +1303,24 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Split a collection into a certain number of groups. + * {@inheritDoc} * - * @param int $numberOfGroups - * @return static + * @throws \InvalidArgumentException */ + #[\Override] public function split($numberOfGroups) { - return $this->passthru('split', func_get_args()); + if ($numberOfGroups < 1) { + throw new InvalidArgumentException('Number of groups must be at least 1.'); + } + + return $this->passthru(__FUNCTION__, func_get_args()); } /** * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1258,10 +1342,31 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable ->sole(); } + /** + * Determine if the collection contains a single item or a single item matching the given criteria. + * + * @param (callable(TValue, TKey): bool)|string|null $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function hasSole($key = null, $operator = null, $value = null): bool + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->unless($filter == null) + ->filter($filter) + ->take(2) + ->count() === 1; + } + /** * Get the first item in the collection but throw an exception if no matching items exist. * - * @param (callable(TValue, TKey): bool)|string $key + * @param (callable(TValue, TKey): bool)|string|null $key * @param mixed $operator * @param mixed $value * @return TValue @@ -1286,22 +1391,28 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Chunk the collection into chunks of the given size. * * @param int $size - * @return static + * @param bool $preserveKeys + * @return ($preserveKeys is true ? static : static>) */ - public function chunk($size) + public function chunk($size, $preserveKeys = true) { if ($size <= 0) { return static::empty(); } - return new static(function () use ($size) { + $add = match ($preserveKeys) { + true => fn (array &$chunk, Traversable $iterator) => $chunk[$iterator->key()] = $iterator->current(), + false => fn (array &$chunk, Traversable $iterator) => $chunk[] = $iterator->current(), + }; + + return new static(function () use ($size, $add) { $iterator = $this->getIterator(); while ($iterator->valid()) { $chunk = []; while (true) { - $chunk[$iterator->key()] = $iterator->current(); + $add($chunk, $iterator); if (count($chunk) < $size) { $iterator->next(); @@ -1325,18 +1436,24 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Split a collection into a certain number of groups, and fill the first groups completely. * * @param int $numberOfGroups - * @return static + * @return ($numberOfGroups is positive-int ? static : never) + * + * @throws \InvalidArgumentException */ public function splitIn($numberOfGroups) { - return $this->chunk(ceil($this->count() / $numberOfGroups)); + if ($numberOfGroups < 1) { + throw new InvalidArgumentException('Number of groups must be at least 1.'); + } + + return $this->chunk((int) ceil($this->count() / $numberOfGroups)); } /** * Chunk the collection into chunks with a callback. * * @param callable(TValue, TKey, Collection): bool $callback - * @return static> + * @return static> */ public function chunkWhile(callable $callback) { @@ -1370,91 +1487,73 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Sort through each item with a callback. - * - * @param (callable(TValue, TValue): int)|null|int $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function sort($callback = null) { - return $this->passthru('sort', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Sort items in descending order. - * - * @param int $options - * @return static + * {@inheritDoc} */ + #[\Override] public function sortDesc($options = SORT_REGULAR) { - return $this->passthru('sortDesc', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Sort the collection using the given callback. - * - * @param array|(callable(TValue, TKey): mixed)|string $callback - * @param int $options - * @param bool $descending - * @return static + * {@inheritDoc} */ + #[\Override] public function sortBy($callback, $options = SORT_REGULAR, $descending = false) { - return $this->passthru('sortBy', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Sort the collection in descending order using the given callback. - * - * @param array|(callable(TValue, TKey): mixed)|string $callback - * @param int $options - * @return static + * {@inheritDoc} */ + #[\Override] public function sortByDesc($callback, $options = SORT_REGULAR) { - return $this->passthru('sortByDesc', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Sort the collection keys. - * - * @param int $options - * @param bool $descending - * @return static + * {@inheritDoc} */ + #[\Override] public function sortKeys($options = SORT_REGULAR, $descending = false) { - return $this->passthru('sortKeys', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Sort the collection keys in descending order. - * - * @param int $options - * @return static + * {@inheritDoc} */ + #[\Override] public function sortKeysDesc($options = SORT_REGULAR) { - return $this->passthru('sortKeysDesc', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** - * Sort the collection keys using a callback. - * - * @param callable(TKey, TKey): int $callback - * @return static + * {@inheritDoc} */ + #[\Override] public function sortKeysUsing(callable $callback) { - return $this->passthru('sortKeysUsing', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } /** * Take the first or last {$limit} items. * * @param int $limit - * @return static + * @return static */ public function take($limit) { @@ -1497,7 +1596,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Take items in the collection until the given condition is met. * * @param TValue|callable(TValue,TKey): bool $value - * @return static + * @return static */ public function takeUntil($value) { @@ -1516,17 +1615,22 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Take items in the collection until a given point in time. + * Take items in the collection until a given point in time, with an optional callback on timeout. * * @param \DateTimeInterface $timeout - * @return static + * @param callable(TValue|null, TKey|null): mixed|null $callback + * @return static */ - public function takeUntilTimeout(DateTimeInterface $timeout) + public function takeUntilTimeout(DateTimeInterface $timeout, ?callable $callback = null) { $timeout = $timeout->getTimestamp(); - return new static(function () use ($timeout) { + return new static(function () use ($timeout, $callback) { if ($this->now() >= $timeout) { + if ($callback) { + $callback(null, null); + } + return; } @@ -1534,6 +1638,10 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable yield $key => $value; if ($this->now() >= $timeout) { + if ($callback) { + $callback($value, $key); + } + break; } } @@ -1544,7 +1652,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Take items in the collection while the given condition is met. * * @param TValue|callable(TValue,TKey): bool $value - * @return static + * @return static */ public function takeWhile($value) { @@ -1558,7 +1666,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * Pass each item in the collection to the given callback, lazily. * * @param callable(TValue, TKey): mixed $callback - * @return static + * @return static */ public function tapEach(callable $callback) { @@ -1572,23 +1680,45 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Flatten a multi-dimensional associative array with dots. + * Throttle the values, releasing them at most once per the given seconds. * - * @return static + * @return static */ - public function dot() + public function throttle(float $seconds) { - return $this->passthru('dot', []); + return new static(function () use ($seconds) { + $microseconds = $seconds * 1_000_000; + + foreach ($this as $key => $value) { + $fetchedAt = $this->preciseNow(); + + yield $key => $value; + + $sleep = $microseconds - ($this->preciseNow() - $fetchedAt); + + $this->usleep((int) $sleep); + } + }); } /** - * Convert a flatten "dot" notation array into an expanded array. + * Flatten a multi-dimensional associative array with dots. * + * @param int $depth * @return static */ + public function dot($depth = INF) + { + return $this->passthru(__FUNCTION__, [$depth]); + } + + /** + * {@inheritDoc} + */ + #[\Override] public function undot() { - return $this->passthru('undot', []); + return $this->passthru(__FUNCTION__, []); } /** @@ -1596,7 +1726,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable * * @param (callable(TValue, TKey): mixed)|string|null $key * @param bool $strict - * @return static + * @return static */ public function unique($key = null, $strict = false) { @@ -1629,6 +1759,42 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable }); } + /** + * Run the given callback every time the interval has passed. + * + * @return static + */ + public function withHeartbeat(DateInterval|int $interval, callable $callback) + { + $seconds = is_int($interval) ? $interval : $this->intervalSeconds($interval); + + return new static(function () use ($seconds, $callback) { + $start = $this->now(); + + foreach ($this as $key => $value) { + $now = $this->now(); + + if (($now - $start) >= $seconds) { + $callback(); + + $start = $now; + } + + yield $key => $value; + } + }); + } + + /** + * Get the total seconds from the given interval. + */ + protected function intervalSeconds(DateInterval $interval): int + { + $start = new DateTimeImmutable(); + + return $start->add($interval)->getTimestamp() - $start->getTimestamp(); + } + /** * Zip the collection together with one or more arrays. * @@ -1645,9 +1811,9 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable $iterables = func_get_args(); return new static(function () use ($iterables) { - $iterators = Collection::make($iterables)->map(function ($iterable) { - return $this->makeIterator($iterable); - })->prepend($this->getIterator()); + $iterators = (new Collection($iterables)) + ->map(fn ($iterable) => $this->makeIterator($iterable)) + ->prepend($this->getIterator()); while ($iterators->contains->valid()) { yield new static($iterators->map->current()); @@ -1658,18 +1824,13 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable } /** - * Pad collection to the specified length with a value. - * - * @template TPadValue - * - * @param int $size - * @param TPadValue $value - * @return static + * {@inheritDoc} */ + #[\Override] public function pad($size, $value) { if ($size < 0) { - return $this->passthru('pad', func_get_args()); + return $this->passthru(__FUNCTION__, func_get_args()); } return new static(function () use ($size, $value) { @@ -1752,7 +1913,7 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable { $value = is_string($value) ? explode('.', $value) : $value; - $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + $key = is_null($key) || is_array($key) || $key instanceof Closure ? $key : explode('.', $key); return [$value, $key]; } @@ -1782,4 +1943,32 @@ class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable ? Carbon::now()->timestamp : time(); } + + /** + * Get the precise current time. + * + * @return float + */ + protected function preciseNow() + { + return class_exists(Carbon::class) + ? Carbon::now()->getPreciseTimestamp() + : microtime(true) * 1_000_000; + } + + /** + * Sleep for the given amount of microseconds. + * + * @return void + */ + protected function usleep(int $microseconds) + { + if ($microseconds <= 0) { + return; + } + + class_exists(Sleep::class) + ? Sleep::usleep($microseconds) + : usleep($microseconds); + } } diff --git a/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php b/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php index d90d835b4..9c5c7c560 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php +++ b/data/web/inc/lib/vendor/illuminate/collections/MultipleItemsFoundException.php @@ -19,7 +19,6 @@ class MultipleItemsFoundException extends RuntimeException * @param int $count * @param int $code * @param \Throwable|null $previous - * @return void */ public function __construct($count, $code = 0, $previous = null) { diff --git a/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php b/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php index 9718f5bc5..a475ee10b 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php +++ b/data/web/inc/lib/vendor/illuminate/collections/Traits/EnumeratesValues.php @@ -2,6 +2,7 @@ namespace Illuminate\Support\Traits; +use BackedEnum; use CachingIterator; use Closure; use Exception; @@ -11,48 +12,49 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Enumerable; use Illuminate\Support\HigherOrderCollectionProxy; -use InvalidArgumentException; use JsonSerializable; -use Symfony\Component\VarDumper\VarDumper; -use Traversable; use UnexpectedValueException; use UnitEnum; -use WeakMap; + +use function Illuminate\Support\enum_value; /** * @template TKey of array-key * * @template-covariant TValue * - * @property-read HigherOrderCollectionProxy $average - * @property-read HigherOrderCollectionProxy $avg - * @property-read HigherOrderCollectionProxy $contains - * @property-read HigherOrderCollectionProxy $doesntContain - * @property-read HigherOrderCollectionProxy $each - * @property-read HigherOrderCollectionProxy $every - * @property-read HigherOrderCollectionProxy $filter - * @property-read HigherOrderCollectionProxy $first - * @property-read HigherOrderCollectionProxy $flatMap - * @property-read HigherOrderCollectionProxy $groupBy - * @property-read HigherOrderCollectionProxy $keyBy - * @property-read HigherOrderCollectionProxy $map - * @property-read HigherOrderCollectionProxy $max - * @property-read HigherOrderCollectionProxy $min - * @property-read HigherOrderCollectionProxy $partition - * @property-read HigherOrderCollectionProxy $percentage - * @property-read HigherOrderCollectionProxy $reject - * @property-read HigherOrderCollectionProxy $skipUntil - * @property-read HigherOrderCollectionProxy $skipWhile - * @property-read HigherOrderCollectionProxy $some - * @property-read HigherOrderCollectionProxy $sortBy - * @property-read HigherOrderCollectionProxy $sortByDesc - * @property-read HigherOrderCollectionProxy $sum - * @property-read HigherOrderCollectionProxy $takeUntil - * @property-read HigherOrderCollectionProxy $takeWhile - * @property-read HigherOrderCollectionProxy $unique - * @property-read HigherOrderCollectionProxy $unless - * @property-read HigherOrderCollectionProxy $until - * @property-read HigherOrderCollectionProxy $when + * @property-read HigherOrderCollectionProxy $average + * @property-read HigherOrderCollectionProxy $avg + * @property-read HigherOrderCollectionProxy $contains + * @property-read HigherOrderCollectionProxy $doesntContain + * @property-read HigherOrderCollectionProxy $each + * @property-read HigherOrderCollectionProxy $every + * @property-read HigherOrderCollectionProxy $filter + * @property-read HigherOrderCollectionProxy $first + * @property-read HigherOrderCollectionProxy $flatMap + * @property-read HigherOrderCollectionProxy $groupBy + * @property-read HigherOrderCollectionProxy $hasMany + * @property-read HigherOrderCollectionProxy $hasSole + * @property-read HigherOrderCollectionProxy $keyBy + * @property-read HigherOrderCollectionProxy $last + * @property-read HigherOrderCollectionProxy $map + * @property-read HigherOrderCollectionProxy $max + * @property-read HigherOrderCollectionProxy $min + * @property-read HigherOrderCollectionProxy $partition + * @property-read HigherOrderCollectionProxy $percentage + * @property-read HigherOrderCollectionProxy $reject + * @property-read HigherOrderCollectionProxy $skipUntil + * @property-read HigherOrderCollectionProxy $skipWhile + * @property-read HigherOrderCollectionProxy $some + * @property-read HigherOrderCollectionProxy $sortBy + * @property-read HigherOrderCollectionProxy $sortByDesc + * @property-read HigherOrderCollectionProxy $sum + * @property-read HigherOrderCollectionProxy $takeUntil + * @property-read HigherOrderCollectionProxy $takeWhile + * @property-read HigherOrderCollectionProxy $unique + * @property-read HigherOrderCollectionProxy $unless + * @property-read HigherOrderCollectionProxy $until + * @property-read HigherOrderCollectionProxy $when */ trait EnumeratesValues { @@ -81,7 +83,10 @@ trait EnumeratesValues 'first', 'flatMap', 'groupBy', + 'hasMany', + 'hasSole', 'keyBy', + 'last', 'map', 'max', 'min', @@ -175,6 +180,41 @@ trait EnumeratesValues ->map($callback); } + /** + * Create a new collection by decoding a JSON string. + * + * @param string $json + * @param int $depth + * @param int $flags + * @return static + */ + public static function fromJson($json, $depth = 512, $flags = 0) + { + return new static(json_decode($json, true, $depth, $flags)); + } + + /** + * Get the average value of a given key. + * + * @param (callable(TValue): float|int)|string|null $callback + * @return float|int|null + */ + public function avg($callback = null) + { + $callback = $this->valueRetriever($callback); + + $reduced = $this->reduce(static function (&$reduce, $value) use ($callback) { + if (! is_null($resolved = $callback($value))) { + $reduce[0] += $resolved; + $reduce[1]++; + } + + return $reduce; + }, [0, 0]); + + return $reduced[1] ? $reduced[0] / $reduced[1] : null; + } + /** * Alias for the "avg" method. * @@ -200,30 +240,25 @@ trait EnumeratesValues } /** - * Dump the items and end the script. + * Dump the given arguments and terminate execution. * * @param mixed ...$args * @return never */ public function dd(...$args) { - $this->dump(...$args); - - exit(1); + dd($this->all(), ...$args); } /** * Dump the items. * + * @param mixed ...$args * @return $this */ - public function dump() + public function dump(...$args) { - (new Collection(func_get_args())) - ->push($this->all()) - ->each(function ($item) { - VarDumper::dump($item); - }); + dump($this->all(), ...$args); return $this; } @@ -298,6 +333,27 @@ trait EnumeratesValues return $this->first($this->operatorForWhere(...func_get_args())); } + /** + * Determine if the collection contains multiple items, optionally matching the given criteria. + * + * @param (callable(TValue, TKey): bool)|string|null $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function hasMany($key = null, $operator = null, $value = null): bool + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->unless($filter == null) + ->filter($filter) + ->take(2) + ->count() === 2; + } + /** * Get a single key's value from the first matching item in the collection. * @@ -309,11 +365,11 @@ trait EnumeratesValues */ public function value($key, $default = null) { - if ($value = $this->firstWhere($key)) { - return data_get($value, $key, $default); - } + $value = $this->first(function ($target) use ($key) { + return data_has($target, $key); + }); - return value($default); + return data_get($value, $key, $default); } /** @@ -321,7 +377,7 @@ trait EnumeratesValues * * @template TEnsureOfType * - * @param class-string|array> $type + * @param class-string|array>|'string'|'int'|'float'|'bool'|'array'|'null' $type * @return static * * @throws \UnexpectedValueException @@ -330,7 +386,7 @@ trait EnumeratesValues { $allowedTypes = is_array($type) ? $type : [$type]; - return $this->each(function ($item) use ($allowedTypes) { + return $this->each(function ($item, $index) use ($allowedTypes) { $itemType = get_debug_type($item); foreach ($allowedTypes as $allowedType) { @@ -340,7 +396,7 @@ trait EnumeratesValues } throw new UnexpectedValueException( - sprintf("Collection should only include [%s] items, but '%s' found.", implode(', ', $allowedTypes), $itemType) + sprintf("Collection should only include [%s] items, but '%s' found at position %d.", implode(', ', $allowedTypes), $itemType, $index) ); }); } @@ -348,6 +404,12 @@ trait EnumeratesValues /** * Determine if the collection is not empty. * + * @phpstan-assert-if-true TValue $this->first() + * @phpstan-assert-if-true TValue $this->last() + * + * @phpstan-assert-if-false null $this->first() + * @phpstan-assert-if-false null $this->last() + * * @return bool */ public function isNotEmpty() @@ -387,7 +449,7 @@ trait EnumeratesValues { $groups = $this->mapToDictionary($callback); - return $groups->map([$this, 'make']); + return $groups->map($this->make(...)); } /** @@ -414,6 +476,10 @@ trait EnumeratesValues */ public function mapInto($class) { + if (is_subclass_of($class, BackedEnum::class)) { + return $this->map(fn ($value, $key) => $class::from($value)); + } + return $this->map(fn ($value, $key) => new $class($value, $key)); } @@ -428,7 +494,7 @@ trait EnumeratesValues $callback = $this->valueRetriever($callback); return $this->map(fn ($value) => $callback($value)) - ->filter(fn ($value) => ! is_null($value)) + ->reject(fn ($value) => is_null($value)) ->reduce(fn ($result, $value) => is_null($result) || $value < $result ? $value : $result); } @@ -442,7 +508,7 @@ trait EnumeratesValues { $callback = $this->valueRetriever($callback); - return $this->filter(fn ($value) => ! is_null($value))->reduce(function ($result, $item) use ($callback) { + return $this->reject(fn ($value) => is_null($value))->reduce(function ($result, $item) use ($callback) { $value = $callback($item); return is_null($result) || $value > $result ? $value : $result; @@ -467,26 +533,17 @@ trait EnumeratesValues * Partition the collection into two arrays using the given callback or key. * * @param (callable(TValue, TKey): bool)|TValue|string $key - * @param TValue|string|null $operator - * @param TValue|null $value + * @param mixed $operator + * @param mixed $value * @return static, static> */ public function partition($key, $operator = null, $value = null) { - $passed = []; - $failed = []; - $callback = func_num_args() === 1 - ? $this->valueRetriever($key) - : $this->operatorForWhere(...func_get_args()); + ? $this->valueRetriever($key) + : $this->operatorForWhere(...func_get_args()); - foreach ($this as $key => $item) { - if ($callback($item, $key)) { - $passed[$key] = $item; - } else { - $failed[$key] = $item; - } - } + [$passed, $failed] = Arr::partition($this->getIterator(), $callback); return new static([new static($passed), new static($failed)]); } @@ -513,8 +570,10 @@ trait EnumeratesValues /** * Get the sum of the given values. * - * @param (callable(TValue): mixed)|string|null $callback - * @return mixed + * @template TReturnType + * + * @param (callable(TValue): TReturnType)|string|null $callback + * @return ($callback is callable ? TReturnType : mixed) */ public function sum($callback = null) { @@ -767,7 +826,7 @@ trait EnumeratesValues */ public function pipeThrough($callbacks) { - return Collection::make($callbacks)->reduce( + return (new Collection($callbacks))->reduce( fn ($carry, $callback) => $callback($carry), $this, ); @@ -927,15 +986,12 @@ trait EnumeratesValues public function jsonSerialize(): array { return array_map(function ($value) { - if ($value instanceof JsonSerializable) { - return $value->jsonSerialize(); - } elseif ($value instanceof Jsonable) { - return json_decode($value->toJson(), true); - } elseif ($value instanceof Arrayable) { - return $value->toArray(); - } - - return $value; + return match (true) { + $value instanceof JsonSerializable => $value->jsonSerialize(), + $value instanceof Jsonable => json_decode($value->toJson(), true), + $value instanceof Arrayable => $value->toArray(), + default => $value, + }; }, $this->all()); } @@ -950,6 +1006,17 @@ trait EnumeratesValues return json_encode($this->jsonSerialize(), $options); } + /** + * Get the collection of items as pretty print formatted JSON. + * + * @param int $options + * @return string + */ + public function toPrettyJson(int $options = 0) + { + return $this->toJson(JSON_PRETTY_PRINT | $options); + } + /** * Get a CachingIterator instance. * @@ -969,8 +1036,8 @@ trait EnumeratesValues public function __toString() { return $this->escapeWhenCastingToString - ? e($this->toJson()) - : $this->toJson(); + ? e($this->toJson()) + : $this->toJson(); } /** @@ -1022,20 +1089,9 @@ trait EnumeratesValues */ protected function getArrayableItems($items) { - if (is_array($items)) { - return $items; - } - - return match (true) { - $items instanceof WeakMap => throw new InvalidArgumentException('Collections can not be created using instances of WeakMap.'), - $items instanceof Enumerable => $items->all(), - $items instanceof Arrayable => $items->toArray(), - $items instanceof Traversable => iterator_to_array($items), - $items instanceof Jsonable => json_decode($items->toJson(), true), - $items instanceof JsonSerializable => (array) $items->jsonSerialize(), - $items instanceof UnitEnum => [$items], - default => (array) $items, - }; + return is_null($items) || is_scalar($items) || $items instanceof UnitEnum + ? Arr::wrap($items) + : Arr::from($items); } /** @@ -1065,10 +1121,15 @@ trait EnumeratesValues } return function ($item) use ($key, $operator, $value) { - $retrieved = data_get($item, $key); + $retrieved = enum_value(data_get($item, $key)); + $value = enum_value($value); $strings = array_filter([$retrieved, $value], function ($value) { - return is_string($value) || (is_object($value) && method_exists($value, '__toString')); + return match (true) { + is_string($value) => true, + $value instanceof \Stringable => true, + default => false, + }; }); if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { diff --git a/data/web/inc/lib/vendor/illuminate/collections/Traits/TransformsToResourceCollection.php b/data/web/inc/lib/vendor/illuminate/collections/Traits/TransformsToResourceCollection.php new file mode 100644 index 000000000..86aaebd4b --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/collections/Traits/TransformsToResourceCollection.php @@ -0,0 +1,121 @@ +|null $resourceClass + * @return \Illuminate\Http\Resources\Json\ResourceCollection + * + * @throws \Throwable + */ + public function toResourceCollection(?string $resourceClass = null): ResourceCollection + { + if ($resourceClass === null) { + return $this->guessResourceCollection(); + } + + return $resourceClass::collection($this); + } + + /** + * Guess the resource collection for the items. + * + * @return \Illuminate\Http\Resources\Json\ResourceCollection + * + * @throws \Throwable + */ + protected function guessResourceCollection(): ResourceCollection + { + if ($this->isEmpty()) { + return new ResourceCollection($this); + } + + $model = $this->items[0] ?? null; + + throw_unless(is_object($model), LogicException::class, 'Resource collection guesser expects the collection to contain objects.'); + + /** @var class-string $className */ + $className = get_class($model); + + throw_unless(method_exists($className, 'guessResourceName'), LogicException::class, sprintf('Expected class %s to implement guessResourceName method. Make sure the model uses the TransformsToResource trait.', $className)); + + $useResourceCollection = $this->resolveResourceCollectionFromAttribute($className); + + if ($useResourceCollection !== null && class_exists($useResourceCollection)) { + return new $useResourceCollection($this); + } + + $useResource = $this->resolveResourceFromAttribute($className); + + if ($useResource !== null && class_exists($useResource)) { + return $useResource::collection($this); + } + + $resourceClasses = $className::guessResourceName(); + + foreach ($resourceClasses as $resourceClass) { + $resourceCollection = $resourceClass.'Collection'; + + if (is_string($resourceCollection) && class_exists($resourceCollection)) { + return new $resourceCollection($this); + } + } + + foreach ($resourceClasses as $resourceClass) { + if (is_string($resourceClass) && class_exists($resourceClass)) { + return $resourceClass::collection($this); + } + } + + throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className)); + } + + /** + * Get the resource class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\JsonResource> $class + * @return class-string<*>|null + */ + protected function resolveResourceFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResource::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } + + /** + * Get the resource collection class from the class attribute. + * + * @param class-string<\Illuminate\Http\Resources\Json\ResourceCollection> $class + * @return class-string<*>|null + */ + protected function resolveResourceCollectionFromAttribute(string $class): ?string + { + if (! class_exists($class)) { + return null; + } + + $attributes = (new ReflectionClass($class))->getAttributes(UseResourceCollection::class); + + return $attributes !== [] + ? $attributes[0]->newInstance()->class + : null; + } +} diff --git a/data/web/inc/lib/vendor/illuminate/collections/composer.json b/data/web/inc/lib/vendor/illuminate/collections/composer.json index 5e11a788e..2471710b3 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/composer.json +++ b/data/web/inc/lib/vendor/illuminate/collections/composer.json @@ -14,26 +14,31 @@ } ], "require": { - "php": "^8.1", - "illuminate/conditionable": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/macroable": "^10.0" + "php": "^8.2", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33" }, "autoload": { "psr-4": { "Illuminate\\Support\\": "" }, "files": [ + "functions.php", "helpers.php" ] }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "suggest": { - "symfony/var-dumper": "Required to use the dump method (^6.2)." + "illuminate/http": "Required to convert collections to API resources (^12.0).", + "symfony/var-dumper": "Required to use the dump method (^7.2)." }, "config": { "sort-packages": true diff --git a/data/web/inc/lib/vendor/illuminate/collections/helpers.php b/data/web/inc/lib/vendor/illuminate/collections/helpers.php index 235edceb1..ea9b1ab1d 100644 --- a/data/web/inc/lib/vendor/illuminate/collections/helpers.php +++ b/data/web/inc/lib/vendor/illuminate/collections/helpers.php @@ -13,7 +13,7 @@ if (! function_exists('collect')) { * @param \Illuminate\Contracts\Support\Arrayable|iterable|null $value * @return \Illuminate\Support\Collection */ - function collect($value = []) + function collect($value = []): Collection { return new Collection($value); } @@ -34,6 +34,36 @@ if (! function_exists('data_fill')) { } } +if (! function_exists('data_has')) { + /** + * Determine if a key / property exists on an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int|null $key + * @return bool + */ + function data_has($target, $key): bool + { + if (is_null($key) || $key === []) { + return false; + } + + $key = is_array($key) ? $key : explode('.', $key); + + foreach ($key as $segment) { + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && property_exists($target, $segment)) { + $target = $target->{$segment}; + } else { + return false; + } + } + + return true; + } +} + if (! function_exists('data_get')) { /** * Get an item from an array or object using "dot" notation. @@ -74,6 +104,15 @@ if (! function_exists('data_get')) { return in_array('*', $key) ? Arr::collapse($result) : $result; } + $segment = match ($segment) { + '\*' => '*', + '\{first}' => '{first}', + '{first}' => array_key_first(Arr::from($target)), + '\{last}' => '{last}', + '{last}' => array_key_last(Arr::from($target)), + default => $segment, + }; + if (Arr::accessible($target) && Arr::exists($target, $segment)) { $target = $target[$segment]; } elseif (is_object($target) && isset($target->{$segment})) { @@ -194,7 +233,7 @@ if (! function_exists('head')) { */ function head($array) { - return reset($array); + return empty($array) ? false : array_first($array); } } @@ -207,7 +246,7 @@ if (! function_exists('last')) { */ function last($array) { - return end($array); + return empty($array) ? false : array_last($array); } } @@ -215,12 +254,40 @@ if (! function_exists('value')) { /** * Return the default value of the given value. * - * @param mixed $value - * @param mixed ...$args - * @return mixed + * @template TValue + * @template TArgs + * + * @param TValue|\Closure(TArgs): TValue $value + * @param TArgs ...$args + * @return TValue */ function value($value, ...$args) { return $value instanceof Closure ? $value(...$args) : $value; } } + +if (! function_exists('when')) { + /** + * Return a value if the given condition is true. + * + * @template TValue + * @template TArgs + * @template TDefault + * + * @param mixed $condition + * @param TValue|\Closure(TArgs): TValue $value + * @param TDefault|\Closure(): TDefault $default + * @return ($condition is true|positive-int|non-falsy-string|non-empty-array ? TValue : ($condition is callable ? TValue|TDefault : TDefault)) + */ + function when($condition, $value, $default = null) + { + $condition = $condition instanceof Closure ? $condition() : $condition; + + if ($condition) { + return value($value, $condition); + } + + return value($default, $condition); + } +} diff --git a/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php b/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php index 579114cf1..0a694c24f 100644 --- a/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php +++ b/data/web/inc/lib/vendor/illuminate/conditionable/HigherOrderWhenProxy.php @@ -36,7 +36,6 @@ class HigherOrderWhenProxy * Create a new proxy instance. * * @param mixed $target - * @return void */ public function __construct($target) { diff --git a/data/web/inc/lib/vendor/illuminate/conditionable/composer.json b/data/web/inc/lib/vendor/illuminate/conditionable/composer.json index 40b080806..9e0ddfbbd 100644 --- a/data/web/inc/lib/vendor/illuminate/conditionable/composer.json +++ b/data/web/inc/lib/vendor/illuminate/conditionable/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.0.2" + "php": "^8.2" }, "autoload": { "psr-4": { @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php index cedeb6ea3..6b8a0441c 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Authorizable.php @@ -8,7 +8,7 @@ interface Authorizable * Determine if the entity has a given ability. * * @param iterable|string $abilities - * @param array|mixed $arguments + * @param mixed $arguments * @return bool */ public function can($abilities, $arguments = []); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Gate.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Gate.php index eb605d827..dd43d69a5 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Gate.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Access/Gate.php @@ -7,7 +7,7 @@ interface Gate /** * Determine if a given ability has been defined. * - * @param string $ability + * @param \UnitEnum|string $ability * @return bool */ public function has($ability); @@ -15,7 +15,7 @@ interface Gate /** * Define a new ability. * - * @param string $ability + * @param \UnitEnum|string $ability * @param callable|string $callback * @return $this */ @@ -29,7 +29,7 @@ interface Gate * @param array|null $abilities * @return $this */ - public function resource($name, $class, array $abilities = null); + public function resource($name, $class, ?array $abilities = null); /** * Define a policy class for a given class type. @@ -59,8 +59,8 @@ interface Gate /** * Determine if all of the given abilities should be granted for the current user. * - * @param iterable|string $ability - * @param array|mixed $arguments + * @param iterable|\UnitEnum|string $ability + * @param mixed $arguments * @return bool */ public function allows($ability, $arguments = []); @@ -68,8 +68,8 @@ interface Gate /** * Determine if any of the given abilities should be denied for the current user. * - * @param iterable|string $ability - * @param array|mixed $arguments + * @param iterable|\UnitEnum|string $ability + * @param mixed $arguments * @return bool */ public function denies($ability, $arguments = []); @@ -77,8 +77,8 @@ interface Gate /** * Determine if all of the given abilities should be granted for the current user. * - * @param iterable|string $abilities - * @param array|mixed $arguments + * @param iterable|\UnitEnum|string $abilities + * @param mixed $arguments * @return bool */ public function check($abilities, $arguments = []); @@ -86,8 +86,8 @@ interface Gate /** * Determine if any one of the given abilities should be granted for the current user. * - * @param iterable|string $abilities - * @param array|mixed $arguments + * @param iterable|\UnitEnum|string $abilities + * @param mixed $arguments * @return bool */ public function any($abilities, $arguments = []); @@ -95,8 +95,8 @@ interface Gate /** * Determine if the given ability should be granted for the current user. * - * @param string $ability - * @param array|mixed $arguments + * @param \UnitEnum|string $ability + * @param mixed $arguments * @return \Illuminate\Auth\Access\Response * * @throws \Illuminate\Auth\Access\AuthorizationException @@ -106,8 +106,8 @@ interface Gate /** * Inspect the user for the given ability. * - * @param string $ability - * @param array|mixed $arguments + * @param \UnitEnum|string $ability + * @param mixed $arguments * @return \Illuminate\Auth\Access\Response */ public function inspect($ability, $arguments = []); @@ -116,7 +116,7 @@ interface Gate * Get the raw result from the authorization callback. * * @param string $ability - * @param array|mixed $arguments + * @param mixed $arguments * @return mixed * * @throws \Illuminate\Auth\Access\AuthorizationException diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Authenticatable.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Authenticatable.php index ac4ed886d..f5ed4987b 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Authenticatable.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Authenticatable.php @@ -18,6 +18,13 @@ interface Authenticatable */ public function getAuthIdentifier(); + /** + * Get the name of the password attribute for the user. + * + * @return string + */ + public function getAuthPasswordName(); + /** * Get the password for the user. * diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Guard.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Guard.php index 2796f1ae6..a3541f42b 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/Guard.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/Guard.php @@ -51,7 +51,7 @@ interface Guard * Set the current user. * * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @return void + * @return $this */ public function setUser(Authenticatable $user); } diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/PasswordBroker.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/PasswordBroker.php index bbbe9b508..c6b202329 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/PasswordBroker.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/PasswordBroker.php @@ -48,7 +48,7 @@ interface PasswordBroker * @param \Closure|null $callback * @return string */ - public function sendResetLink(array $credentials, Closure $callback = null); + public function sendResetLink(array $credentials, ?Closure $callback = null); /** * Reset the password for the given token. diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/StatefulGuard.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/StatefulGuard.php index faf1497d5..2448cf9b0 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/StatefulGuard.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/StatefulGuard.php @@ -35,7 +35,7 @@ interface StatefulGuard extends Guard * * @param mixed $id * @param bool $remember - * @return \Illuminate\Contracts\Auth\Authenticatable|bool + * @return \Illuminate\Contracts\Auth\Authenticatable|false */ public function loginUsingId($id, $remember = false); @@ -43,7 +43,7 @@ interface StatefulGuard extends Guard * Log the given user ID into the application without sessions or cookies. * * @param mixed $id - * @return \Illuminate\Contracts\Auth\Authenticatable|bool + * @return \Illuminate\Contracts\Auth\Authenticatable|false */ public function onceUsingId($id); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Auth/UserProvider.php b/data/web/inc/lib/vendor/illuminate/contracts/Auth/UserProvider.php index a2ab12271..dd9bb4194 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Auth/UserProvider.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Auth/UserProvider.php @@ -19,7 +19,7 @@ interface UserProvider * @param string $token * @return \Illuminate\Contracts\Auth\Authenticatable|null */ - public function retrieveByToken($identifier, $token); + public function retrieveByToken($identifier, #[\SensitiveParameter] $token); /** * Update the "remember me" token for the given user in storage. @@ -28,7 +28,7 @@ interface UserProvider * @param string $token * @return void */ - public function updateRememberToken(Authenticatable $user, $token); + public function updateRememberToken(Authenticatable $user, #[\SensitiveParameter] $token); /** * Retrieve a user by the given credentials. @@ -36,7 +36,7 @@ interface UserProvider * @param array $credentials * @return \Illuminate\Contracts\Auth\Authenticatable|null */ - public function retrieveByCredentials(array $credentials); + public function retrieveByCredentials(#[\SensitiveParameter] array $credentials); /** * Validate a user against the given credentials. @@ -45,5 +45,15 @@ interface UserProvider * @param array $credentials * @return bool */ - public function validateCredentials(Authenticatable $user, array $credentials); + public function validateCredentials(Authenticatable $user, #[\SensitiveParameter] array $credentials); + + /** + * Rehash the user's password if required and supported. + * + * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param array $credentials + * @param bool $force + * @return void + */ + public function rehashPasswordIfRequired(Authenticatable $user, #[\SensitiveParameter] array $credentials, bool $force = false); } diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Broadcasting/ShouldRescue.php b/data/web/inc/lib/vendor/illuminate/contracts/Broadcasting/ShouldRescue.php new file mode 100644 index 000000000..fad874030 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Broadcasting/ShouldRescue.php @@ -0,0 +1,8 @@ + $id + * @return ($id is class-string ? TClass : mixed) + */ + public function get(string $id); + /** * Determine if the given abstract type has been bound. * @@ -30,7 +40,7 @@ interface Container extends ContainerInterface * Assign a set of tags to a given binding. * * @param array|string $abstracts - * @param array|mixed ...$tags + * @param mixed ...$tags * @return void */ public function tag($abstracts, $tags); @@ -46,7 +56,7 @@ interface Container extends ContainerInterface /** * Register a binding with the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void @@ -65,7 +75,7 @@ interface Container extends ContainerInterface /** * Register a binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void @@ -75,7 +85,7 @@ interface Container extends ContainerInterface /** * Register a shared binding in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -84,7 +94,7 @@ interface Container extends ContainerInterface /** * Register a shared binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -93,7 +103,7 @@ interface Container extends ContainerInterface /** * Register a scoped binding in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -102,7 +112,7 @@ interface Container extends ContainerInterface /** * Register a scoped binding if it hasn't already been registered. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string|null $concrete * @return void */ @@ -111,7 +121,7 @@ interface Container extends ContainerInterface /** * "Extend" an abstract type in the container. * - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure $closure * @return void * @@ -122,9 +132,11 @@ interface Container extends ContainerInterface /** * Register an existing instance as shared in the container. * - * @param string $abstract - * @param mixed $instance - * @return mixed + * @template TInstance of mixed + * + * @param \Closure|string $abstract + * @param TInstance $instance + * @return TInstance */ public function instance($abstract, $instance); @@ -132,7 +144,7 @@ interface Container extends ContainerInterface * Add a contextual binding to the container. * * @param string $concrete - * @param string $abstract + * @param \Closure|string $abstract * @param \Closure|string $implementation * @return void */ @@ -149,8 +161,10 @@ interface Container extends ContainerInterface /** * Get a closure to resolve the given type from the container. * - * @param string $abstract - * @return \Closure + * @template TClass of object + * + * @param string|class-string $abstract + * @return ($abstract is class-string ? \Closure(): TClass : \Closure(): mixed) */ public function factory($abstract); @@ -164,9 +178,11 @@ interface Container extends ContainerInterface /** * Resolve the given type from the container. * - * @param string $abstract + * @template TClass of object + * + * @param string|class-string $abstract * @param array $parameters - * @return mixed + * @return ($abstract is class-string ? TClass : mixed) * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ @@ -197,7 +213,7 @@ interface Container extends ContainerInterface * @param \Closure|null $callback * @return void */ - public function beforeResolving($abstract, Closure $callback = null); + public function beforeResolving($abstract, ?Closure $callback = null); /** * Register a new resolving callback. @@ -206,7 +222,7 @@ interface Container extends ContainerInterface * @param \Closure|null $callback * @return void */ - public function resolving($abstract, Closure $callback = null); + public function resolving($abstract, ?Closure $callback = null); /** * Register a new after resolving callback. @@ -215,5 +231,5 @@ interface Container extends ContainerInterface * @param \Closure|null $callback * @return void */ - public function afterResolving($abstract, Closure $callback = null); + public function afterResolving($abstract, ?Closure $callback = null); } diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Container/ContextualAttribute.php b/data/web/inc/lib/vendor/illuminate/contracts/Container/ContextualAttribute.php new file mode 100644 index 000000000..06f6f06b8 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Container/ContextualAttribute.php @@ -0,0 +1,8 @@ +|CastsAttributes|CastsInboundAttributes */ public static function castUsing(array $arguments); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php index 6a3e2ef18..312f7aed4 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php @@ -12,7 +12,7 @@ interface CastsInboundAttributes * @param \Illuminate\Database\Eloquent\Model $model * @param string $key * @param mixed $value - * @param array $attributes + * @param array $attributes * @return mixed */ public function set(Model $model, string $key, mixed $value, array $attributes); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/ComparesCastableAttributes.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/ComparesCastableAttributes.php new file mode 100644 index 000000000..5c9ad195b --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/ComparesCastableAttributes.php @@ -0,0 +1,19 @@ + $attributes * @return mixed */ public function serialize(Model $model, string $key, mixed $value, array $attributes); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php index c82125aa0..19cb15a82 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php @@ -9,7 +9,7 @@ interface SupportsPartialRelations * * @param string|null $column * @param string|\Closure|null $aggregate - * @param string $relation + * @param string|null $relation * @return $this */ public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Database/LostConnectionDetector.php b/data/web/inc/lib/vendor/illuminate/contracts/Database/LostConnectionDetector.php new file mode 100644 index 000000000..bd4012ca9 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Database/LostConnectionDetector.php @@ -0,0 +1,16 @@ +|string|null */ public $class; @@ -37,23 +44,26 @@ class ModelIdentifier /** * The class name of the model collection. * - * @var string|null + * @var class-string<\Illuminate\Database\Eloquent\Collection>|null */ public $collectionClass; /** * Create a new model identifier. * - * @param string $class + * @param class-string<\Illuminate\Database\Eloquent\Model>|null $class * @param mixed $id * @param array $relations * @param mixed $connection - * @return void */ public function __construct($class, $id, array $relations, $connection) { - $this->id = $id; + if ($class !== null && self::$useMorphMap) { + $class = Relation::getMorphAlias($class); + } + $this->class = $class; + $this->id = $id; $this->relations = $relations; $this->connection = $connection; } @@ -61,7 +71,7 @@ class ModelIdentifier /** * Specify the collection class that should be used when serializing / restoring collections. * - * @param string|null $collectionClass + * @param class-string<\Illuminate\Database\Eloquent\Collection> $collectionClass * @return $this */ public function useCollectionClass(?string $collectionClass) @@ -70,4 +80,26 @@ class ModelIdentifier return $this; } + + /** + * Get the fully-qualified class name of the Model. + * + * @return class-string<\Illuminate\Database\Eloquent\Model>|null + */ + public function getClass(): ?string + { + if (self::$useMorphMap && $this->class !== null) { + return Relation::getMorphedModel($this->class) ?? $this->class; + } + + return $this->class; + } + + /** + * Indicate whether to use the relational morph-map when serializing Models. + */ + public static function useMorphMap(bool $useMorphMap = true): void + { + static::$useMorphMap = $useMorphMap; + } } diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Debug/ShouldntReport.php b/data/web/inc/lib/vendor/illuminate/contracts/Debug/ShouldntReport.php new file mode 100644 index 000000000..dab84fc8c --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Debug/ShouldntReport.php @@ -0,0 +1,8 @@ + */ public function files($directory = null, $recursive = false); @@ -152,7 +181,7 @@ interface Filesystem * Get all of the files from the given directory (recursive). * * @param string|null $directory - * @return array + * @return array */ public function allFiles($directory = null); @@ -161,7 +190,7 @@ interface Filesystem * * @param string|null $directory * @param bool $recursive - * @return array + * @return array */ public function directories($directory = null, $recursive = false); @@ -169,7 +198,7 @@ interface Filesystem * Get all (recursive) of the directories within a given directory. * * @param string|null $directory - * @return array + * @return array */ public function allDirectories($directory = null); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Hashing/Hasher.php b/data/web/inc/lib/vendor/illuminate/contracts/Hashing/Hasher.php index b5e4d4c8a..d88cf1669 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Hashing/Hasher.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Hashing/Hasher.php @@ -19,7 +19,7 @@ interface Hasher * @param array $options * @return string */ - public function make($value, array $options = []); + public function make(#[\SensitiveParameter] $value, array $options = []); /** * Check the given plain value against a hash. @@ -29,7 +29,7 @@ interface Hasher * @param array $options * @return bool */ - public function check($value, $hashedValue, array $options = []); + public function check(#[\SensitiveParameter] $value, $hashedValue, array $options = []); /** * Check if the given hash has been hashed using the given options. diff --git a/data/web/inc/lib/vendor/illuminate/contracts/JsonSchema/JsonSchema.php b/data/web/inc/lib/vendor/illuminate/contracts/JsonSchema/JsonSchema.php new file mode 100644 index 000000000..3c327d018 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/JsonSchema/JsonSchema.php @@ -0,0 +1,59 @@ +)|array $properties + * @return \Illuminate\JsonSchema\Types\ObjectType + */ + public function object(Closure|array $properties = []); + + /** + * Create a new array property instance. + * + * @return \Illuminate\JsonSchema\Types\ArrayType + */ + public function array(); + + /** + * Create a new string property instance. + * + * @return \Illuminate\JsonSchema\Types\StringType + */ + public function string(); + + /** + * Create a new integer property instance. + * + * @return \Illuminate\JsonSchema\Types\IntegerType + */ + public function integer(); + + /** + * Create a new number property instance. + * + * @return \Illuminate\JsonSchema\Types\NumberType + */ + public function number(); + + /** + * Create a new boolean property instance. + * + * @return \Illuminate\JsonSchema\Types\BooleanType + */ + public function boolean(); + + /** + * Create a new multi-type union instance. + * + * @param array $types + * @return \Illuminate\JsonSchema\Types\UnionType + */ + public function union(array $types); +} diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Log/ContextLogProcessor.php b/data/web/inc/lib/vendor/illuminate/contracts/Log/ContextLogProcessor.php new file mode 100644 index 000000000..ca6900cfb --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Log/ContextLogProcessor.php @@ -0,0 +1,9 @@ + */ public function items(); @@ -92,6 +99,13 @@ interface CursorPaginator */ public function hasPages(); + /** + * Determine if there are more items in the data source. + * + * @return bool + */ + public function hasMorePages(); + /** * Get the base path for paginator generated URLs. * diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Pagination/LengthAwarePaginator.php b/data/web/inc/lib/vendor/illuminate/contracts/Pagination/LengthAwarePaginator.php index 4e6ef7197..bd080821f 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Pagination/LengthAwarePaginator.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Pagination/LengthAwarePaginator.php @@ -2,6 +2,13 @@ namespace Illuminate\Contracts\Pagination; +/** + * @template TKey of array-key + * + * @template-covariant TValue + * + * @extends Paginator + */ interface LengthAwarePaginator extends Paginator { /** diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Pagination/Paginator.php b/data/web/inc/lib/vendor/illuminate/contracts/Pagination/Paginator.php index 04201cef4..409ea1d60 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Pagination/Paginator.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Pagination/Paginator.php @@ -2,6 +2,13 @@ namespace Illuminate\Contracts\Pagination; +/** + * @template TKey of array-key + * + * @template-covariant TValue + * + * @method $this through(callable(TValue): mixed $callback) + */ interface Paginator { /** @@ -29,6 +36,13 @@ interface Paginator */ public function fragment($fragment = null); + /** + * Add all current query string values to the paginator. + * + * @return $this + */ + public function withQueryString(); + /** * The URL for the next page, or null. * @@ -46,7 +60,7 @@ interface Paginator /** * Get all of the items being paginated. * - * @return array + * @return array */ public function items(); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Pipeline/Pipeline.php b/data/web/inc/lib/vendor/illuminate/contracts/Pipeline/Pipeline.php index 226081c75..e7dd6e027 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Pipeline/Pipeline.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Pipeline/Pipeline.php @@ -7,23 +7,23 @@ use Closure; interface Pipeline { /** - * Set the traveler object being sent on the pipeline. + * Set the object being sent through the pipeline. * - * @param mixed $traveler + * @param mixed $passable * @return $this */ - public function send($traveler); + public function send($passable); /** - * Set the stops of the pipeline. + * Set the array of pipes. * - * @param dynamic|array $stops + * @param mixed $pipes * @return $this */ - public function through($stops); + public function through($pipes); /** - * Set the method to call on the stops. + * Set the method to call on the pipes. * * @param string $method * @return $this diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Process/InvokedProcess.php b/data/web/inc/lib/vendor/illuminate/contracts/Process/InvokedProcess.php index d272d3772..99746d783 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Process/InvokedProcess.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Process/InvokedProcess.php @@ -58,7 +58,15 @@ interface InvokedProcess * Wait for the process to finish. * * @param callable|null $output - * @return \Illuminate\Console\Process\ProcessResult + * @return \Illuminate\Process\ProcessResult */ - public function wait(callable $output = null); + public function wait(?callable $output = null); + + /** + * Wait until the given callback returns true. + * + * @param callable|null $output + * @return \Illuminate\Process\ProcessResult + */ + public function waitUntil(?callable $output = null); } diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Process/ProcessResult.php b/data/web/inc/lib/vendor/illuminate/contracts/Process/ProcessResult.php index 702701ea1..5f0d65154 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Process/ProcessResult.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Process/ProcessResult.php @@ -39,6 +39,14 @@ interface ProcessResult */ public function output(); + /** + * Determine if the output contains the given string. + * + * @param string $output + * @return bool + */ + public function seeInOutput(string $output); + /** * Get the error output of the process. * @@ -46,13 +54,21 @@ interface ProcessResult */ public function errorOutput(); + /** + * Determine if the error output contains the given string. + * + * @param string $output + * @return bool + */ + public function seeInErrorOutput(string $output); + /** * Throw an exception if the process failed. * * @param callable|null $callback * @return $this */ - public function throw(callable $callback = null); + public function throw(?callable $callback = null); /** * Throw an exception if the process failed and the given condition is true. @@ -61,5 +77,5 @@ interface ProcessResult * @param callable|null $callback * @return $this */ - public function throwIf(bool $condition, callable $callback = null); + public function throwIf(bool $condition, ?callable $callback = null); } diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Queue/EntityNotFoundException.php b/data/web/inc/lib/vendor/illuminate/contracts/Queue/EntityNotFoundException.php index 079250d06..0b966a728 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Queue/EntityNotFoundException.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Queue/EntityNotFoundException.php @@ -11,7 +11,6 @@ class EntityNotFoundException extends InvalidArgumentException * * @param string $type * @param mixed $id - * @return void */ public function __construct($type, $id) { diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Queue/Job.php b/data/web/inc/lib/vendor/illuminate/contracts/Queue/Job.php index 9efd17d14..68f9d0bec 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Queue/Job.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Queue/Job.php @@ -133,7 +133,7 @@ interface Job public function getName(); /** - * Get the resolved name of the queued job class. + * Get the display name of the queued job class. * * Resolves the name of "wrapped" jobs such as class-based handlers. * @@ -141,6 +141,15 @@ interface Job */ public function resolveName(); + /** + * Get the class of the queued job. + * + * Resolves the class of "wrapped" jobs such as class-based handlers. + * + * @return string + */ + public function resolveQueuedJobClass(); + /** * Get the name of the connection the job belongs to. * diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Queue/Queue.php b/data/web/inc/lib/vendor/illuminate/contracts/Queue/Queue.php index 1994cddf7..816f8a762 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Queue/Queue.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Queue/Queue.php @@ -2,6 +2,12 @@ namespace Illuminate\Contracts\Queue; +/** + * @method int pendingSize(string|null $queue = null) + * @method int delayedSize(string|null $queue = null) + * @method int reservedSize(string|null $queue = null) + * @method int|null creationTimeOfOldestPendingJob(string|null $queue = null) + */ interface Queue { /** @@ -37,7 +43,6 @@ interface Queue * * @param string $payload * @param string|null $queue - * @param array $options * @return mixed */ public function pushRaw($payload, $queue = null, array $options = []); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Redis/Factory.php b/data/web/inc/lib/vendor/illuminate/contracts/Redis/Factory.php index c2bfe81f9..fdb965090 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Redis/Factory.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Redis/Factory.php @@ -7,7 +7,7 @@ interface Factory /** * Get a Redis connection by name. * - * @param string|null $name + * @param \UnitEnum|string|null $name * @return \Illuminate\Redis\Connections\Connection */ public function connection($name = null); diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Routing/ResponseFactory.php b/data/web/inc/lib/vendor/illuminate/contracts/Routing/ResponseFactory.php index 0aa51d551..95dcf96bc 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Routing/ResponseFactory.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Routing/ResponseFactory.php @@ -67,6 +67,17 @@ interface ResponseFactory */ public function stream($callback, $status = 200, array $headers = []); + /** + * Create a new streamed JSON response instance. + * + * @param array $data + * @param int $status + * @param array $headers + * @param int $encodingOptions + * @return \Symfony\Component\HttpFoundation\StreamedJsonResponse + */ + public function streamJson($data, $status = 200, $headers = [], $encodingOptions = 15); + /** * Create a new streamed response instance as a file download. * @@ -112,7 +123,7 @@ interface ResponseFactory /** * Create a new redirect response to a named route. * - * @param string $route + * @param \BackedEnum|string $route * @param mixed $parameters * @param int $status * @param array $headers diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Routing/UrlGenerator.php b/data/web/inc/lib/vendor/illuminate/contracts/Routing/UrlGenerator.php index cca221cc7..ed159066c 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Routing/UrlGenerator.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Routing/UrlGenerator.php @@ -59,6 +59,41 @@ interface UrlGenerator */ public function route($name, $parameters = [], $absolute = true); + /** + * Create a signed route URL for a named route. + * + * @param string $name + * @param mixed $parameters + * @param \DateTimeInterface|\DateInterval|int|null $expiration + * @param bool $absolute + * @return string + * + * @throws \InvalidArgumentException + */ + public function signedRoute($name, $parameters = [], $expiration = null, $absolute = true); + + /** + * Create a temporary signed route URL for a named route. + * + * @param string $name + * @param \DateTimeInterface|\DateInterval|int $expiration + * @param array $parameters + * @param bool $absolute + * @return string + */ + public function temporarySignedRoute($name, $expiration, $parameters = [], $absolute = true); + + /** + * Generate an absolute URL with the given query parameters. + * + * @param string $path + * @param array $query + * @param mixed $extra + * @param bool|null $secure + * @return string + */ + public function query($path, $query = [], $extra = [], $secure = null); + /** * Get the URL to a controller action. * diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Session/Session.php b/data/web/inc/lib/vendor/illuminate/contracts/Session/Session.php index 1bf025a09..4fea003cf 100644 --- a/data/web/inc/lib/vendor/illuminate/contracts/Session/Session.php +++ b/data/web/inc/lib/vendor/illuminate/contracts/Session/Session.php @@ -98,6 +98,15 @@ interface Session */ public function put($key, $value = null); + /** + * Flash a key / value pair to the session. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function flash(string $key, $value = true); + /** * Get the CSRF token value. * diff --git a/data/web/inc/lib/vendor/illuminate/contracts/Support/HasOnceHash.php b/data/web/inc/lib/vendor/illuminate/contracts/Support/HasOnceHash.php new file mode 100644 index 000000000..e9ec6e594 --- /dev/null +++ b/data/web/inc/lib/vendor/illuminate/contracts/Support/HasOnceHash.php @@ -0,0 +1,13 @@ +bindTo($this, static::class); + try { + $macro = $macro->bindTo($this, static::class) ?? throw new RuntimeException; + } catch (Throwable) { + $macro = $macro->bindTo(null, static::class); + } } return $macro(...$parameters); diff --git a/data/web/inc/lib/vendor/illuminate/macroable/composer.json b/data/web/inc/lib/vendor/illuminate/macroable/composer.json index 231d2333a..38dc6f161 100644 --- a/data/web/inc/lib/vendor/illuminate/macroable/composer.json +++ b/data/web/inc/lib/vendor/illuminate/macroable/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^8.1" + "php": "^8.2" }, "autoload": { "psr-4": { @@ -23,7 +23,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "12.x-dev" } }, "config": { diff --git a/data/web/inc/lib/vendor/league/oauth2-client/LICENSE b/data/web/inc/lib/vendor/league/oauth2-client/LICENSE index 7dfa39b7c..9c8958afc 100644 --- a/data/web/inc/lib/vendor/league/oauth2-client/LICENSE +++ b/data/web/inc/lib/vendor/league/oauth2-client/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2020 Alex Bilbie +Copyright (c) 2013-2023 Alex Bilbie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/data/web/inc/lib/vendor/league/oauth2-client/README.md b/data/web/inc/lib/vendor/league/oauth2-client/README.md index cbb449d48..05c8d457e 100644 --- a/data/web/inc/lib/vendor/league/oauth2-client/README.md +++ b/data/web/inc/lib/vendor/league/oauth2-client/README.md @@ -24,14 +24,16 @@ This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. I We support the following versions of PHP: +* PHP 8.5 +* PHP 8.4 +* PHP 8.3 +* PHP 8.2 * PHP 8.1 * PHP 8.0 * PHP 7.4 * PHP 7.3 * PHP 7.2 * PHP 7.1 -* PHP 7.0 -* PHP 5.6 ## Provider Clients diff --git a/data/web/inc/lib/vendor/league/oauth2-client/composer.json b/data/web/inc/lib/vendor/league/oauth2-client/composer.json index 59201f48f..927770446 100644 --- a/data/web/inc/lib/vendor/league/oauth2-client/composer.json +++ b/data/web/inc/lib/vendor/league/oauth2-client/composer.json @@ -6,15 +6,16 @@ "sort-packages": true }, "require": { - "php": "^5.6 || ^7.0 || ^8.0", - "guzzlehttp/guzzle": "^6.0 || ^7.0", - "paragonie/random_compat": "^1 || ^2 || ^9.99" + "php": "^7.1 || >=8.0.0 <8.6.0", + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.8.2 || ^8.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.6.3 || ^3.0" }, "require-dev": { "mockery/mockery": "^1.3.5", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", - "squizlabs/php_codesniffer": "^2.3 || ^3.0" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" }, "keywords": [ "oauth", @@ -49,10 +50,5 @@ "psr-4": { "League\\OAuth2\\Client\\Test\\": "test/src/" } - }, - "extra": { - "branch-alias": { - "dev-2.x": "2.0.x-dev" - } } } diff --git a/data/web/inc/lib/vendor/league/oauth2-client/phpunit.xml.dist b/data/web/inc/lib/vendor/league/oauth2-client/phpunit.xml.dist new file mode 100644 index 000000000..e63822b2f --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/phpunit.xml.dist @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + test + + + + + + + + + + src + + + + + + + + diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php index 293a54d6e..eb6241764 100644 --- a/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Provider/AbstractProvider.php @@ -17,6 +17,7 @@ namespace League\OAuth2\Client\Provider; use GuzzleHttp\Client as HttpClient; use GuzzleHttp\ClientInterface as HttpClientInterface; use GuzzleHttp\Exception\BadResponseException; +use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; use League\OAuth2\Client\Grant\AbstractGrant; use League\OAuth2\Client\Grant\GrantFactory; @@ -405,6 +406,7 @@ abstract class AbstractProvider * * @param array $options * @return array Authorization parameters + * @throws InvalidArgumentException */ protected function getAuthorizationParameters(array $options) { @@ -476,6 +478,7 @@ abstract class AbstractProvider * * @param array $options * @return string Authorization URL + * @throws InvalidArgumentException */ public function getAuthorizationUrl(array $options = []) { @@ -492,10 +495,11 @@ abstract class AbstractProvider * @param array $options * @param callable|null $redirectHandler * @return mixed + * @throws InvalidArgumentException */ public function authorize( array $options = [], - callable $redirectHandler = null + ?callable $redirectHandler = null ) { $url = $this->getAuthorizationUrl($options); if ($redirectHandler) { @@ -613,13 +617,20 @@ abstract class AbstractProvider * * @param mixed $grant * @param array $options - * @throws IdentityProviderException * @return AccessTokenInterface + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException */ public function getAccessToken($grant, array $options = []) { $grant = $this->verifyGrant($grant); + if (isset($options['scope']) && is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + $params = [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, @@ -700,6 +711,7 @@ abstract class AbstractProvider * * @param RequestInterface $request * @return ResponseInterface + * @throws GuzzleException */ public function getResponse(RequestInterface $request) { @@ -710,8 +722,10 @@ abstract class AbstractProvider * Sends a request and returns the parsed response. * * @param RequestInterface $request - * @throws IdentityProviderException * @return mixed + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException */ public function getParsedResponse(RequestInterface $request) { @@ -757,7 +771,7 @@ abstract class AbstractProvider */ protected function getContentType(ResponseInterface $response) { - return join(';', (array) $response->getHeader('content-type')); + return implode(';', $response->getHeader('content-type')); } /** @@ -815,7 +829,7 @@ abstract class AbstractProvider * Custom mapping of expiration, etc should be done here. Always call the * parent method when overloading this method. * - * @param mixed $result + * @param array $result * @return array */ protected function prepareAccessTokenResponse(array $result) @@ -859,6 +873,9 @@ abstract class AbstractProvider * * @param AccessToken $token * @return ResourceOwnerInterface + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException */ public function getResourceOwner(AccessToken $token) { @@ -872,6 +889,9 @@ abstract class AbstractProvider * * @param AccessToken $token * @return mixed + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException */ protected function fetchResourceOwnerDetails(AccessToken $token) { diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php index 81533c307..331b0ace6 100644 --- a/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/AccessToken.php @@ -22,7 +22,7 @@ use RuntimeException; * * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4) */ -class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface +class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface, SettableRefreshTokenInterface { /** * @var string @@ -118,7 +118,7 @@ class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInter } elseif (!empty($options['expires'])) { // Some providers supply the seconds until expiration rather than // the exact timestamp. Take a best guess at which we received. - $expires = $options['expires']; + $expires = (int) $options['expires']; if (!$this->isExpirationTimestamp($expires)) { $expires += $this->getTimeNow(); @@ -169,6 +169,14 @@ class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInter return $this->refreshToken; } + /** + * @inheritdoc + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + } + /** * @inheritdoc */ @@ -196,7 +204,7 @@ class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInter throw new RuntimeException('"expires" is not set on the token'); } - return $expires < time(); + return $expires < $this->getTimeNow(); } /** diff --git a/data/web/inc/lib/vendor/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php new file mode 100644 index 000000000..491bee991 --- /dev/null +++ b/data/web/inc/lib/vendor/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ + +namespace League\OAuth2\Client\Token; + +interface SettableRefreshTokenInterface +{ + /** + * Sets or replaces the refresh token with the provided refresh token. + * + * @param string $refreshToken + * @return void + */ + public function setRefreshToken($refreshToken); +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/.github/FUNDING.yml b/data/web/inc/lib/vendor/matthiasmullie/minify/.github/FUNDING.yml deleted file mode 100644 index 9b9868875..000000000 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: [matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md b/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md deleted file mode 100644 index 226cf976b..000000000 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/CONTRIBUTING.md +++ /dev/null @@ -1,59 +0,0 @@ -# How to contribute - - -## Issues - -When [filing bugs](https://github.com/matthiasmullie/minify/issues/new), -try to be as thorough as possible: -* What version did you use? -* What did you try to do? ***Please post the relevant parts of your code.*** -* What went wrong? ***Please include error messages, if any.*** -* What was the expected result? - - -## Pull requests - -Bug fixes and general improvements to the existing codebase are always welcome. -New features are also welcome, but will be judged on an individual basis. If -you'd rather not risk wasting your time implementing a new feature only to see -it turned down, please start the discussion by -[opening an issue](https://github.com/matthiasmullie/minify/issues/new). - -Don't forget to add your changes to the [changelog](CHANGELOG.md). - - -### Testing - -Please include tests for every change or addition to the code. -To run the complete test suite: - -```sh -vendor/bin/phpunit -``` - -When submitting a new pull request, please make sure that that the test suite -passes (Travis CI will run it & report back on your pull request.) - -To run the tests on Windows, run `tests/convert_symlinks_to_windows_style.sh` -from the command line in order to convert Linux-style test symlinks to -Windows-style. - - -### Coding standards - -All code must follow [PSR-2](http://www.php-fig.org/psr/psr-2/). Just make sure -to run php-cs-fixer before submitting the code, it'll take care of the -formatting for you: - -```sh -vendor/bin/php-cs-fixer fix src -vendor/bin/php-cs-fixer fix tests -``` - -Document the code thoroughly! - - -## License - -Note that minify is MIT-licensed, which basically allows anyone to do -anything they like with it, without restriction. diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile b/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile deleted file mode 100644 index 5917e05f4..000000000 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -ARG version=cli -FROM php:$version - -COPY . /var/www -WORKDIR /var/www - -RUN apt-get update -RUN apt-get install -y zip unzip libzip-dev git -RUN docker-php-ext-install zip -RUN docker-php-ext-install pcntl -RUN curl -sS https://getcomposer.org/installer | php -RUN mv composer.phar /usr/local/bin/composer -RUN composer install diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json b/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json index a6dd4ab6a..9c47b4011 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/composer.json @@ -3,12 +3,12 @@ "type": "library", "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", "keywords": ["minify", "minifier", "css", "js", "javascript"], - "homepage": "http://www.minifier.org", + "homepage": "https://github.com/matthiasmullie/minify", "license": "MIT", "authors": [ { "name": "Matthias Mullie", - "homepage": "http://www.mullie.eu", + "homepage": "https://www.mullie.eu", "email": "minify@mullie.eu", "role": "Developer" } @@ -19,9 +19,9 @@ "matthiasmullie/path-converter": "~1.1" }, "require-dev": { - "matthiasmullie/scrapbook": "dev-master", - "phpunit/phpunit": ">=4.8", - "friendsofphp/php-cs-fixer": "~2.0" + "friendsofphp/php-cs-fixer": ">=2.0", + "matthiasmullie/scrapbook": ">=1.3", + "phpunit/phpunit": ">=4.8" }, "suggest": { "psr/cache-implementation": "Cache implementation to use with Minify::cache" @@ -31,6 +31,11 @@ "MatthiasMullie\\Minify\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "MatthiasMullie\\Minify\\Tests\\": "tests/" + } + }, "bin": [ "bin/minifycss", "bin/minifyjs" diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml b/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml deleted file mode 100644 index cb7ace102..000000000 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/docker-compose.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '2.1' -services: - php: - build: - context: . - dockerfile: Dockerfile - volumes: - - ./src:/var/www/src - - ./data:/var/www/data - - ./tests:/var/www/tests - - ./phpunit.xml.dist:/var/www/phpunit.xml.dist - '8.0': - extends: php - build: - args: - version: 8.0-cli - '7.4': - extends: php - build: - args: - version: 7.4-cli - '7.3': - extends: php - build: - args: - version: 7.3-cli - '7.2': - extends: php - build: - args: - version: 7.2-cli - '7.1': - extends: php - build: - args: - version: 7.1-cli - '7.0': - extends: php - build: - args: - version: 7.0-cli - '5.6': - extends: php - build: - args: - version: 5.6-cli diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php index eb98e52ed..34f6bc910 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/CSS.php @@ -1,6 +1,7 @@ * @author Tijs Verkoyen * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved @@ -44,6 +45,10 @@ class CSS extends Minify 'jpeg' => 'data:image/jpeg', 'svg' => 'data:image/svg+xml', 'woff' => 'data:application/x-font-woff', + 'woff2' => 'data:application/x-font-woff2', + 'avif' => 'data:image/avif', + 'apng' => 'data:image/apng', + 'webp' => 'data:image/webp', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'xbm' => 'image/x-xbitmap', @@ -93,7 +98,7 @@ class CSS extends Minify } // add to top - $content = implode(';', $matches[2]).';'.trim($content, ';'); + $content = implode(';', $matches[2]) . ';' . trim($content, ';'); } return $content; @@ -102,11 +107,11 @@ class CSS extends Minify /** * Combine CSS from import statements. * - * @import's will be loaded and their content merged into the original file, + * \@import's will be loaded and their content merged into the original file, * to save HTTP requests. * - * @param string $source The file to combine imports for - * @param string $content The CSS content to combine imports for + * @param string $source The file to combine imports for + * @param string $content The CSS content to combine imports for * @param string[] $parents Parent paths, for circular reference checks * * @return string @@ -200,7 +205,7 @@ class CSS extends Minify // loop the matches foreach ($matches as $match) { // get the path for the file that will be imported - $importPath = dirname($source).'/'.$match['path']; + $importPath = dirname($source) . '/' . $match['path']; // only replace the import with the content if we can grab the // content of the file @@ -211,7 +216,7 @@ class CSS extends Minify // check if current file was not imported previously in the same // import chain. if (in_array($importPath, $parents)) { - throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.'); + throw new FileImportException('Failed to import file "' . $importPath . '": circular reference detected.'); } // grab referenced file & minify it (which may include importing @@ -223,7 +228,7 @@ class CSS extends Minify // check if this is only valid for certain media if (!empty($match['media'])) { - $importContent = '@media '.$match['media'].'{'.$importContent.'}'; + $importContent = '@media ' . $match['media'] . '{' . $importContent . '}'; } // add to replacement array @@ -241,7 +246,7 @@ class CSS extends Minify * @url(image.jpg) images will be loaded and their content merged into the * original file, to save HTTP requests. * - * @param string $source The file to import files for + * @param string $source The file to import files for * @param string $content The CSS content to import files for * * @return string @@ -262,7 +267,7 @@ class CSS extends Minify // get the path for the file that will be imported $path = $match[2]; - $path = dirname($source).'/'.$path; + $path = dirname($source) . '/' . $path; // only replace the import with the content if we're able to get // the content of the file, and it's relatively small @@ -273,7 +278,7 @@ class CSS extends Minify // build replacement $search[] = $match[0]; - $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')'; + $replace[] = 'url(' . $this->importExtensions[$extension] . ';base64,' . $importContent . ')'; } } @@ -289,9 +294,11 @@ class CSS extends Minify * Perform CSS optimizations. * * @param string[optional] $path Path to write the data to - * @param string[] $parents Parent paths, for circular reference checks + * @param string[] $parents Parent paths, for circular reference checks * * @return string The minified data + * + * @throws PatternMatchException */ public function execute($path = null, $parents = array()) { @@ -312,7 +319,9 @@ class CSS extends Minify $css = $this->replace($css); $css = $this->stripWhitespace($css); - $css = $this->shortenColors($css); + $css = $this->convertLegacyColors($css); + $css = $this->cleanupModernColors($css); + $css = $this->shortenHEXColors($css); $css = $this->shortenZeroes($css); $css = $this->shortenFontWeights($css); $css = $this->stripEmptyTags($css); @@ -351,7 +360,7 @@ class CSS extends Minify * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). * * @param ConverterInterface $converter Relative path converter - * @param string $content The CSS content to update relative urls for + * @param string $content The CSS content to update relative urls for * * @return string */ @@ -465,9 +474,9 @@ class CSS extends Minify // build replacement $search[] = $match[0]; if ($type === 'url') { - $replace[] = 'url('.$url.')'; + $replace[] = 'url(' . $url . ')'; } elseif ($type === 'import') { - $replace[] = '@import "'.$url.'"'; + $replace[] = '@import "' . $url . '"'; } } @@ -476,64 +485,154 @@ class CSS extends Minify } /** - * Shorthand hex color codes. - * #FF0000 -> #F00. + * Shorthand HEX color codes. + * #FF0000FF -> #f00 -> red + * #FF00FF00 -> transparent. * - * @param string $content The CSS content to shorten the hex color codes for + * @param string $content The CSS content to shorten the HEX color codes for * * @return string */ - protected function shortenColors($content) + protected function shortenHexColors($content) { - $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); + // shorten repeating patterns within HEX .. + $content = preg_replace('/(?<=[: ])#([0-9a-f])\\1([0-9a-f])\\2([0-9a-f])\\3(?:([0-9a-f])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); - // remove alpha channel if it's pointless... - $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content); - $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content); + // remove alpha channel if it's pointless .. + $content = preg_replace('/(?<=[: ])#([0-9a-f]{6})ff(?=[; }])/i', '#$1', $content); + $content = preg_replace('/(?<=[: ])#([0-9a-f]{3})f(?=[; }])/i', '#$1', $content); + + // replace `transparent` with shortcut .. + $content = preg_replace('/(?<=[: ])#[0-9a-f]{6}00(?=[; }])/i', '#fff0', $content); $colors = array( + // make these more readable + '#00f' => 'blue', + '#dc143c' => 'crimson', + '#0ff' => 'cyan', + '#8b0000' => 'darkred', + '#696969' => 'dimgray', + '#ff69b4' => 'hotpink', + '#0f0' => 'lime', + '#fdf5e6' => 'oldlace', + '#87ceeb' => 'skyblue', + '#d8bfd8' => 'thistle', // we can shorten some even more by replacing them with their color name - '#F0FFFF' => 'azure', - '#F5F5DC' => 'beige', - '#A52A2A' => 'brown', - '#FF7F50' => 'coral', - '#FFD700' => 'gold', + '#f0ffff' => 'azure', + '#f5f5dc' => 'beige', + '#ffe4c4' => 'bisque', + '#a52a2a' => 'brown', + '#ff7f50' => 'coral', + '#ffd700' => 'gold', '#808080' => 'gray', '#008000' => 'green', - '#4B0082' => 'indigo', - '#FFFFF0' => 'ivory', - '#F0E68C' => 'khaki', - '#FAF0E6' => 'linen', + '#4b0082' => 'indigo', + '#fffff0' => 'ivory', + '#f0e68c' => 'khaki', + '#faf0e6' => 'linen', '#800000' => 'maroon', '#000080' => 'navy', '#808000' => 'olive', - '#CD853F' => 'peru', - '#FFC0CB' => 'pink', - '#DDA0DD' => 'plum', + '#ffa500' => 'orange', + '#da70d6' => 'orchid', + '#cd853f' => 'peru', + '#ffc0cb' => 'pink', + '#dda0dd' => 'plum', '#800080' => 'purple', - '#F00' => 'red', - '#FA8072' => 'salmon', - '#A0522D' => 'sienna', - '#C0C0C0' => 'silver', - '#FFFAFA' => 'snow', - '#D2B48C' => 'tan', - '#FF6347' => 'tomato', - '#EE82EE' => 'violet', - '#F5DEB3' => 'wheat', + '#f00' => 'red', + '#fa8072' => 'salmon', + '#a0522d' => 'sienna', + '#c0c0c0' => 'silver', + '#fffafa' => 'snow', + '#d2b48c' => 'tan', + '#008080' => 'teal', + '#ff6347' => 'tomato', + '#ee82ee' => 'violet', + '#f5deb3' => 'wheat', // or the other way around - 'WHITE' => '#fff', - 'BLACK' => '#000', + 'black' => '#000', + 'fuchsia' => '#f0f', + 'magenta' => '#f0f', + 'white' => '#fff', + 'yellow' => '#ff0', + // and also `transparent` + 'transparent' => '#fff0', ); return preg_replace_callback( - '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i', + '/(?<=[: ])(' . implode('|', array_keys($colors)) . ')(?=[; }])/i', function ($match) use ($colors) { - return $colors[strtoupper($match[0])]; + return $colors[strtolower($match[0])]; }, $content ); } + /** + * Convert RGB|HSL color codes. + * rgb(255,0,0,.5) -> rgb(255 0 0 / .5). + * rgb(255,0,0) -> #f00. + * + * @param string $content The CSS content to shorten the RGB color codes for + * + * @return string + */ + protected function convertLegacyColors($content) + { + /* + https://drafts.csswg.org/css-color/#color-syntax-legacy + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl + */ + + // convert legacy color syntax + $content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content); + $content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*\)/i', '$1($2 $3 $4)', $content); + $content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content); + $content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*\)/i', '$1($2 $3 $4)', $content); + + // convert `rgb` to `hex` + $dec = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; + + return preg_replace_callback( + "/rgb\($dec $dec $dec\)/i", + function ($match) { + return sprintf('#%02x%02x%02x', $match[1], $match[2], $match[3]); + }, + $content + ); + } + + /** + * Cleanup RGB|HSL|HWB|LCH|LAB + * rgb(255 0 0 / 1) -> rgb(255 0 0). + * rgb(255 0 0 / 0) -> transparent. + * + * @param string $content The CSS content to cleanup HSL|HWB|LCH|LAB + * + * @return string + */ + protected function cleanupModernColors($content) + { + /* + https://drafts.csswg.org/css-color/#color-syntax-modern + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab + */ + $tag = '(rgb|hsl|hwb|(?:(?:ok)?(?:lch|lab)))'; + + // remove alpha channel if it's pointless .. + $content = preg_replace('/' . $tag . '\(\s*([^\s)]+)\s+([^\s)]+)\s+([^\s)]+)\s+\/\s+1(?:(?:\.\d?)*|00%)?\s*\)/i', '$1($2 $3 $4)', $content); + + // replace `transparent` with shortcut .. + $content = preg_replace('/' . $tag . '\(\s*[^\s)]+\s+[^\s)]+\s+[^\s)]+\s+\/\s+0(?:[\.0%]*)?\s*\)/i', '#fff0', $content); + + return $content; + } + /** * Shorten CSS font weights. * @@ -549,10 +648,10 @@ class CSS extends Minify ); $callback = function ($match) use ($weights) { - return $match[1].$weights[$match[2]]; + return $match[1] . $weights[$match[2]]; }; - return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content); + return preg_replace_callback('/(font-weight\s*:\s*)(' . implode('|', array_keys($weights)) . ')(?=[;}])/', $callback, $content); } /** @@ -584,19 +683,19 @@ class CSS extends Minify // practice, Webkit (especially Safari) seems to stumble over at least // 0%, potentially other units as well. Only stripping 'px' for now. // @see https://github.com/matthiasmullie/minify/issues/60 - $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content); + $content = preg_replace('/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content); // strip 0-digits (.0 -> 0) - $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content); + $content = preg_replace('/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content); // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px - $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content); + $content = preg_replace('/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content); // strip trailing 0: 50.00 -> 50, 50.00px -> 50px - $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content); + $content = preg_replace('/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content); // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 - $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content); + $content = preg_replace('/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content); // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) - $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content); + $content = preg_replace('/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content); // IE doesn't seem to understand a unitless flex-basis value (correct - // it goes against the spec), so let's add it in again (make it `%`, @@ -628,18 +727,7 @@ class CSS extends Minify */ protected function stripComments() { - // PHP only supports $this inside anonymous functions since 5.4 - $minifier = $this; - $callback = function ($match) use ($minifier) { - $count = count($minifier->extracted); - $placeholder = '/*'.$count.'*/'; - $minifier->extracted[$placeholder] = $match[0]; - - return $placeholder; - }; - $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); - - $this->registerPattern('/\/\*.*?\*\//s', ''); + $this->stripMultilineComments(); } /** @@ -648,29 +736,31 @@ class CSS extends Minify * @param string $content The CSS content to strip the whitespace for * * @return string + * + * @throws PatternMatchException */ protected function stripWhitespace($content) { // remove leading & trailing whitespace - $content = preg_replace('/^\s*/m', '', $content); - $content = preg_replace('/\s*$/m', '', $content); + $content = $this->pregReplace('/^\s*/m', '', $content); + $content = $this->pregReplace('/\s*$/m', '', $content); // replace newlines with a single space - $content = preg_replace('/\s+/', ' ', $content); + $content = $this->pregReplace('/\s+/', ' ', $content); // remove whitespace around meta characters // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex - $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); - $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); - $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); - $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); + $content = $this->pregReplace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); + $content = $this->pregReplace('/([\[(:>\+])\s+/', '$1', $content); + $content = $this->pregReplace('/\s+([\]\)>\+])/', '$1', $content); + $content = $this->pregReplace('/\s+(:)(?![^\}]*\{)/', '$1', $content); // whitespace around + and - can only be stripped inside some pseudo- // classes, like `:nth-child(3+2n)` // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or // selectors like `div.weird- p` $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); - $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); + $content = $this->pregReplace('/:(' . implode('|', $pseudos) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); // remove semicolon/whitespace followed by closing bracket $content = str_replace(';}', '}', $content); @@ -678,14 +768,35 @@ class CSS extends Minify return trim($content); } + /** + * Perform a preg_replace and check for errors. + * + * @param string $pattern Pattern + * @param string $replacement Replacement + * @param string $subject String to process + * + * @return string + * + * @throws PatternMatchException + */ + protected function pregReplace($pattern, $replacement, $subject) + { + $result = preg_replace($pattern, $replacement, $subject); + if ($result === null) { + throw PatternMatchException::fromLastError("Failed to replace with pattern '$pattern'"); + } + + return $result; + } + /** * Replace all occurrences of functions that may contain math, where - * whitespace around operators needs to be preserved (e.g. calc, clamp) + * whitespace around operators needs to be preserved (e.g. calc, clamp). */ protected function extractMath() { $functions = array('calc', 'clamp', 'min', 'max'); - $pattern = '/\b('. implode('|', $functions) .')(\(.+?)(?=$|;|})/m'; + $pattern = '/\b(' . implode('|', $functions) . ')(\(.+?)(?=$|;|})/m'; // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; @@ -700,11 +811,11 @@ class CSS extends Minify // instead, it'll match a larger portion of code to where it's certain that // the calc() musts have ended, and we'll figure out which is the correct // closing parenthesis here, by counting how many have opened - for ($i = 0; $i < $length; $i++) { + for ($i = 0; $i < $length; ++$i) { $char = $match[2][$i]; $expr .= $char; if ($char === '(') { - $opened++; + ++$opened; } elseif ($char === ')' && --$opened === 0) { break; } @@ -712,16 +823,16 @@ class CSS extends Minify // now that we've figured out where the calc() starts and ends, extract it $count = count($minifier->extracted); - $placeholder = 'math('.$count.')'; - $minifier->extracted[$placeholder] = $function.'('.trim(substr($expr, 1, -1)).')'; + $placeholder = 'math(' . $count . ')'; + $minifier->extracted[$placeholder] = $function . '(' . trim(substr($expr, 1, -1)) . ')'; // and since we've captured more code than required, we may have some leftover // calc() in here too - go recursive on the remaining but of code to go figure // that out and extract what is needed - $rest = str_replace($function.$expr, '', $match[0]); + $rest = $minifier->str_replace_first($function . $expr, '', $match[0]); $rest = preg_replace_callback($pattern, $callback, $rest); - return $placeholder.$rest; + return $placeholder . $rest; }; $this->registerPattern($pattern, $callback); @@ -729,19 +840,19 @@ class CSS extends Minify /** * Replace custom properties, whose values may be used in scenarios where - * we wouldn't want them to be minified (e.g. inside calc) + * we wouldn't want them to be minified (e.g. inside calc). */ protected function extractCustomProperties() { // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $this->registerPattern( - '/(?<=^|[;}])(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', + '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', function ($match) use ($minifier) { - $placeholder = '--custom-'. count($minifier->extracted) . ':0'; - $minifier->extracted[$placeholder] = $match[1] .':'. trim($match[2]); - return $placeholder; + $placeholder = '--custom-' . count($minifier->extracted) . ':0'; + $minifier->extracted[$placeholder] = $match[1] . ':' . trim($match[2]); + return $placeholder; } ); } diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php index d03898f04..1c9587c41 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exception.php @@ -1,20 +1,20 @@ */ + namespace MatthiasMullie\Minify; /** - * Base Exception Class + * Base Exception Class. + * * @deprecated Use Exceptions\BasicException instead * - * @package Minify * @author Matthias Mullie */ -abstract class Exception extends \Exception -{ -} +abstract class Exception extends \Exception {} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php index af5e81bc0..ae73f22d7 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/BasicException.php @@ -1,6 +1,7 @@ */ -abstract class BasicException extends Exception -{ -} +abstract class BasicException extends Exception {} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php index 912a2c908..f0b4c6cf0 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php @@ -1,6 +1,7 @@ */ -class FileImportException extends BasicException -{ -} +class FileImportException extends BasicException {} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php index b172eb48f..a1ccedd9d 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/IOException.php @@ -1,6 +1,7 @@ */ -class IOException extends BasicException -{ -} +class IOException extends BasicException {} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/PatternMatchException.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/PatternMatchException.php new file mode 100644 index 000000000..ef41b1a64 --- /dev/null +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/Exceptions/PatternMatchException.php @@ -0,0 +1,36 @@ + + * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved + * @license MIT License + */ + +namespace MatthiasMullie\Minify\Exceptions; + +/** + * Pattern Match Exception Class. + * + * @author Ere Maijala + */ +class PatternMatchException extends BasicException +{ + /** + * Create an exception from preg_last_error. + * + * @param string $msg Error message + */ + public static function fromLastError($msg) + { + $msg .= ': Error ' . preg_last_error(); + if (PHP_MAJOR_VERSION >= 8) { + $msg .= ' - ' . preg_last_error_msg(); + } + + return new PatternMatchException($msg); + } +} diff --git a/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php b/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php index a0fa649d3..806bf0ce8 100644 --- a/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php +++ b/data/web/inc/lib/vendor/matthiasmullie/minify/src/JS.php @@ -1,6 +1,7 @@ * @author Tijs Verkoyen * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved @@ -29,6 +30,8 @@ class JS extends Minify * Note that regular expressions using that bit must have the PCRE_UTF8 * pattern modifier (/u) set. * + * @internal + * * @var string */ const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; @@ -119,21 +122,18 @@ class JS extends Minify */ protected $operatorsAfter = array(); - /** - * {@inheritdoc} - */ public function __construct() { - call_user_func_array(array('parent', '__construct'), func_get_args()); + call_user_func_array(array('\\MatthiasMullie\Minify\\Minify', '__construct'), func_get_args()); - $dataDir = __DIR__.'/../data/js/'; + $dataDir = __DIR__ . '/../data/js/'; $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; - $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options); - $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options); - $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options); - $this->operators = file($dataDir.'operators.txt', $options); - $this->operatorsBefore = file($dataDir.'operators_before.txt', $options); - $this->operatorsAfter = file($dataDir.'operators_after.txt', $options); + $this->keywordsReserved = file($dataDir . 'keywords_reserved.txt', $options); + $this->keywordsBefore = file($dataDir . 'keywords_before.txt', $options); + $this->keywordsAfter = file($dataDir . 'keywords_after.txt', $options); + $this->operators = file($dataDir . 'operators.txt', $options); + $this->operatorsBefore = file($dataDir . 'operators_before.txt', $options); + $this->operatorsAfter = file($dataDir . 'operators_after.txt', $options); } /** @@ -174,7 +174,7 @@ class JS extends Minify $js = $this->stripWhitespace($js); // combine js: separating the scripts by a ; - $content .= $js.";"; + $content .= $js . ';'; } // clean up leftover `;`s from the combination of multiple scripts @@ -195,18 +195,7 @@ class JS extends Minify */ protected function stripComments() { - // PHP only supports $this inside anonymous functions since 5.4 - $minifier = $this; - $callback = function ($match) use ($minifier) { - $count = count($minifier->extracted); - $placeholder = '/*'.$count.'*/'; - $minifier->extracted[$placeholder] = $match[0]; - - return $placeholder; - }; - // multi-line comments - $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); - $this->registerPattern('/\/\*.*?\*\//s', ''); + $this->stripMultilineComments(); // single-line comments $this->registerPattern('/\/\/.*$/m', ''); @@ -235,7 +224,7 @@ class JS extends Minify $minifier = $this; $callback = function ($match) use ($minifier) { $count = count($minifier->extracted); - $placeholder = '"'.$count.'"'; + $placeholder = '"' . $count . '"'; $minifier->extracted[$placeholder] = $match[0]; return $placeholder; @@ -254,7 +243,7 @@ class JS extends Minify // of the RegExp methods (a `\` followed by a variable or value is // likely part of a division, not a regex) $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); - $before = '(^|[=:,;\+\-\*\/\}\(\{\[&\|!]|'.implode('|', $keywords).')\s*'; + $before = '(^|[=:,;\+\-\*\?\/\}\(\{\[&\|!]|' . implode('|', $keywords) . ')\s*'; $propertiesAndMethods = array( // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 'constructor', @@ -274,8 +263,8 @@ class JS extends Minify ); $delimiters = array_fill(0, count($propertiesAndMethods), '/'); $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); - $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))'; - $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + $after = '(?=\s*([\.,;:\)\}&\|+]|\/\/|$|\.(' . implode('|', $propertiesAndMethods) . ')))'; + $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); // regular expressions following a `)` are rather annoying to detect... // quite often, `/` after `)` is a division operator & if it happens to @@ -289,8 +278,8 @@ class JS extends Minify // if a regex following `)` is not followed by `.`, // it's quite likely not a regex $before = '\)\s*'; - $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))'; - $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); + $after = '(?=\s*\.(' . implode('|', $propertiesAndMethods) . '))'; + $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); // 1 more edge case: a regex can be followed by a lot more operators or // keywords if there's a newline (ASI) in between, where the operator @@ -298,8 +287,8 @@ class JS extends Minify // (https://github.com/matthiasmullie/minify/issues/56) $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); - $after = '(?=\s*\n\s*('.implode('|', $operators).'))'; - $this->registerPattern('/'.$pattern.$after.'/', $callback); + $after = '(?=\s*\n\s*(' . implode('|', $operators) . '))'; + $this->registerPattern('/' . $pattern . $after . '/', $callback); } /** @@ -343,8 +332,8 @@ class JS extends Minify unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); $content = preg_replace( array( - '/('.implode('|', $operatorsBefore).')\s+/', - '/\s+('.implode('|', $operatorsAfter).')/', + '/(' . implode('|', $operatorsBefore) . ')\s+/', + '/\s+(' . implode('|', $operatorsAfter) . ')/', ), '\\1', $content @@ -361,8 +350,8 @@ class JS extends Minify ); // collapse whitespace around reserved words into single space - $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content); - $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content); + $content = preg_replace('/(^|[;\}\s])\K(' . implode('|', $keywordsBefore) . ')\s+/', '\\2 ', $content); + $content = preg_replace('/\s+(' . implode('|', $keywordsAfter) . ')(?=([;\{\s]|$))/', ' \\1', $content); /* * We didn't strip whitespace after a couple of operators because they @@ -372,8 +361,8 @@ class JS extends Minify */ $operatorsDiffBefore = array_diff($operators, $operatorsBefore); $operatorsDiffAfter = array_diff($operators, $operatorsAfter); - $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content); - $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content); + $content = preg_replace('/(' . implode('|', $operatorsDiffBefore) . ')[^\S\n]+/', '\\1', $content); + $content = preg_replace('/[^\S\n]+(' . implode('|', $operatorsDiffAfter) . ')/', '\\1', $content); /* * Whitespace after `return` can be omitted in a few occasions @@ -405,9 +394,26 @@ class JS extends Minify * to be the for-loop's body... Same goes for while loops. * I'm going to double that semicolon (if any) so after the next line, * which strips semicolons here & there, we're still left with this one. + * Note the special recursive construct in the three inner parts of the for: + * (\{([^\{\}]*(?-2))*[^\{\}]*\})? - it is intended to match inline + * functions bodies, e.g.: i%&|', $delimiter); - $operators['='] = '(?getKeywordsForRegex($keywords); - $keywords = '(? * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved * @license MIT License @@ -44,6 +46,8 @@ abstract class Minify * been extracted from the JS source code, so we can reliably match "code", * without having to worry about potential "code-like" characters inside. * + * @internal + * * @var string[] */ public $extracted = array(); @@ -128,7 +132,7 @@ abstract class Minify // check if we can read the file if (!$this->canImportFile($path)) { - throw new IOException('The file "'.$path.'" could not be opened for reading. Check if PHP has enough permissions.'); + throw new IOException('The file "' . $path . '" could not be opened for reading. Check if PHP has enough permissions.'); } $this->add($path); @@ -227,7 +231,7 @@ abstract class Minify * Save to file. * * @param string $content The minified data - * @param string $path The path to save the minified data to + * @param string $path The path to save the minified data to * * @throws IOException */ @@ -243,7 +247,10 @@ abstract class Minify /** * Register a pattern to execute against the source content. * - * @param string $pattern PCRE pattern + * If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work. + * If you need that functionality, use a callback instead. + * + * @param string $pattern PCRE pattern * @param string|callable $replacement Replacement value for matched pattern */ protected function registerPattern($pattern, $replacement = '') @@ -254,6 +261,45 @@ abstract class Minify $this->patterns[] = array($pattern, $replacement); } + /** + * Both JS and CSS use the same form of multi-line comment, so putting the common code here. + */ + protected function stripMultilineComments() + { + $minifier = $this; + // Pattern for matching comments that we want to preserve + $keepPattern = '/^ + # comment content + (?: + # either starts with an ! + ! + | + # or, after some number of characters which do not end the comment + (?:(?!\*\/).)*? + + # there is either a @license or @preserve tag + @(?:license|preserve) + ) + /ixs'; + $callback = function ($match) use ($minifier, $keepPattern) { + if (preg_match($keepPattern, $match[1])) { + // Preserve the comment + $count = count($minifier->extracted); + $placeholder = '/*' . $count . '*/'; + $minifier->extracted[$placeholder] = $match[0]; + } else { + // Discard the comment but keep any single line feed + $placeholder = strncmp($match[0], "\n", 1) === 0 || substr($match[0], -1) === "\n" + ? "\n" + : ''; + } + + return $placeholder; + }; + + $this->registerPattern('/\n?\/\*(.*?)\*\/\n?/s', $callback); + } + /** * We can't "just" run some regular expressions against JavaScript: it's a * complex language. E.g. having an occurrence of // xyz would be a comment, @@ -265,14 +311,18 @@ abstract class Minify * @param string $content The content to replace patterns in * * @return string The (manipulated) content + * + * @throws PatternMatchException */ protected function replace($content) { - $processed = ''; + $contentLength = strlen($content); + $output = ''; + $processedOffset = 0; $positions = array_fill(0, count($this->patterns), -1); $matches = array(); - while ($content) { + while ($processedOffset < $contentLength) { // find first match for all patterns foreach ($this->patterns as $i => $pattern) { list($pattern, $replacement) = $pattern; @@ -285,12 +335,13 @@ abstract class Minify // no need to re-run matches that are still in the part of the // content that hasn't been processed - if ($positions[$i] >= 0) { + if ($positions[$i] >= $processedOffset) { continue; } $match = null; - if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) { + $matchResult = preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE, $processedOffset); + if ($matchResult) { $matches[$i] = $match; // we'll store the match position as well; that way, we @@ -298,6 +349,11 @@ abstract class Minify // the first (we'll still know where those others are) $positions[$i] = $match[0][1]; } else { + if ($matchResult === false) { + throw PatternMatchException::fromLastError( + "Failed to match pattern '$pattern' at $processedOffset" + ); + } // if the pattern couldn't be matched, there's no point in // executing it again in later runs on this same content; // ignore this one until we reach end of content @@ -307,61 +363,53 @@ abstract class Minify // no more matches to find: everything's been processed, break out if (!$matches) { - $processed .= $content; + // output the remaining content + $output .= substr($content, $processedOffset); break; } // see which of the patterns actually found the first thing (we'll // only want to execute that one, since we're unsure if what the // other found was not inside what the first found) - $discardLength = min($positions); - $firstPattern = array_search($discardLength, $positions); - $match = $matches[$firstPattern][0][0]; + $matchOffset = min($positions); + $firstPattern = array_search($matchOffset, $positions); + $match = $matches[$firstPattern]; // execute the pattern that matches earliest in the content string - list($pattern, $replacement) = $this->patterns[$firstPattern]; - $replacement = $this->replacePattern($pattern, $replacement, $content); + list(, $replacement) = $this->patterns[$firstPattern]; - // figure out which part of the string was unmatched; that's the - // part we'll execute the patterns on again next - $content = (string) substr($content, $discardLength); - $unmatched = (string) substr($content, strpos($content, $match) + strlen($match)); - - // move the replaced part to $processed and prepare $content to - // again match batch of patterns against - $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched)); - $content = $unmatched; - - // first match has been replaced & that content is to be left alone, - // the next matches will start after this replacement, so we should - // fix their offsets - foreach ($positions as $i => $position) { - $positions[$i] -= $discardLength + strlen($match); - } + // add the part of the input between $processedOffset and the first match; + // that content wasn't matched by anything + $output .= substr($content, $processedOffset, $matchOffset - $processedOffset); + // add the replacement for the match + $output .= $this->executeReplacement($replacement, $match); + // advance $processedOffset past the match + $processedOffset = $matchOffset + strlen($match[0][0]); } - return $processed; + return $output; } /** - * This is where a pattern is matched against $content and the matches - * are replaced by their respective value. - * This function will be called plenty of times, where $content will always - * move up 1 character. + * If $replacement is a callback, execute it, passing in the match data. + * If it's a string, just pass it through. * - * @param string $pattern Pattern to match * @param string|callable $replacement Replacement value - * @param string $content Content to match pattern against + * @param array $match Match data, in PREG_OFFSET_CAPTURE form * * @return string */ - protected function replacePattern($pattern, $replacement, $content) + protected function executeReplacement($replacement, $match) { - if (is_callable($replacement)) { - return preg_replace_callback($pattern, $replacement, $content, 1, $count); - } else { - return preg_replace($pattern, $replacement, $content, 1, $count); + if (!is_callable($replacement)) { + return $replacement; } + // convert $match from the PREG_OFFSET_CAPTURE form to the form the callback expects + foreach ($match as &$matchItem) { + $matchItem = $matchItem[0]; + } + + return $replacement($match); } /** @@ -395,13 +443,18 @@ abstract class Minify } $count = count($minifier->extracted); - $placeholder = $match[1].$placeholderPrefix.$count.$match[1]; - $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1]; + $placeholder = $match[1] . $placeholderPrefix . $count . $match[1]; + $minifier->extracted[$placeholder] = $match[1] . $match[2] . $match[1]; return $placeholder; }; /* + * Quantifier {0,65535} is used instead of *? to avoid exceeding + * backtrack limit with large strings. 65535 is the maximum allowed + * (see https://www.php.net/manual/en/regexp.reference.repetition.php) + * and should be well sufficient for string representations here. + * * The \\ messiness explained: * * Don't count ' or " as end-of-string if it's escaped (has backslash * in front of it) @@ -413,7 +466,8 @@ abstract class Minify * considered as escape-char (times 2) and to get it in the regex, * escaped (times 2) */ - $this->registerPattern('/(['.$chars.'])(.*?(?registerPattern('/([' . $chars . '])(.{0,65535}?(? - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -use PHPStan\BetterReflection\Reflection; -use ReflectionMethod; - -if (!class_exists(AbstractReflectionMacro::class, false)) { - abstract class AbstractReflectionMacro extends AbstractMacro - { - /** - * {@inheritdoc} - */ - public function getReflection(): ?ReflectionMethod - { - if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { - return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); - } - - return $this->reflectionFunction instanceof ReflectionMethod - ? $this->reflectionFunction - : null; - } - } -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php deleted file mode 100644 index bd4c8e804..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/AbstractMacroStatic.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -use PHPStan\BetterReflection\Reflection; -use ReflectionMethod; - -if (!class_exists(AbstractReflectionMacro::class, false)) { - abstract class AbstractReflectionMacro extends AbstractMacro - { - /** - * {@inheritdoc} - */ - public function getReflection(): ?Reflection\Adapter\ReflectionMethod - { - if ($this->reflectionFunction instanceof Reflection\Adapter\ReflectionMethod) { - return $this->reflectionFunction; - } - - if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) { - return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction); - } - - return $this->reflectionFunction instanceof ReflectionMethod - ? new Reflection\Adapter\ReflectionMethod( - Reflection\ReflectionMethod::createFromName( - $this->reflectionFunction->getDeclaringClass()->getName(), - $this->reflectionFunction->getName() - ) - ) - : null; - } - } -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php deleted file mode 100644 index f615b3a64..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroStrongType.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -if (!class_exists(LazyMacro::class, false)) { - abstract class LazyMacro extends AbstractReflectionMacro - { - /** - * {@inheritdoc} - */ - public function getFileName(): ?string - { - $file = $this->reflectionFunction->getFileName(); - - return (($file ? realpath($file) : null) ?: $file) ?: null; - } - - /** - * {@inheritdoc} - */ - public function getStartLine(): ?int - { - return $this->reflectionFunction->getStartLine(); - } - - /** - * {@inheritdoc} - */ - public function getEndLine(): ?int - { - return $this->reflectionFunction->getEndLine(); - } - } -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php deleted file mode 100644 index bf64c1dd9..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/PHPStan/MacroWeakType.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -if (!class_exists(LazyMacro::class, false)) { - abstract class LazyMacro extends AbstractReflectionMacro - { - /** - * {@inheritdoc} - * - * @return string|false - */ - public function getFileName() - { - $file = $this->reflectionFunction->getFileName(); - - return (($file ? realpath($file) : null) ?: $file) ?: null; - } - - /** - * {@inheritdoc} - * - * @return int|false - */ - public function getStartLine() - { - return $this->reflectionFunction->getStartLine(); - } - - /** - * {@inheritdoc} - * - * @return int|false - */ - public function getEndLine() - { - return $this->reflectionFunction->getEndLine(); - } - } -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/ProtectedDatePeriod.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/ProtectedDatePeriod.php new file mode 100644 index 000000000..927e48d21 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/ProtectedDatePeriod.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DatePeriod; + +if (!class_exists(DatePeriodBase::class, false)) { + class DatePeriodBase extends DatePeriod + { + /** + * Period start in PHP < 8.2. + * + * @var CarbonInterface + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period start. + */ + protected $start; + + /** + * Period end in PHP < 8.2. + * + * @var CarbonInterface|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period end. + */ + protected $end; + + /** + * Period current iterated date in PHP < 8.2. + * + * @var CarbonInterface|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period current iterated date. + */ + protected $current; + + /** + * Period interval in PHP < 8.2. + * + * @var CarbonInterval|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period interval. + */ + protected $interval; + + /** + * Period recurrences in PHP < 8.2. + * + * @var int|float|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period recurrences. + */ + protected $recurrences; + + /** + * Period start included option in PHP < 8.2. + * + * @var bool + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period start included option. + */ + protected $include_start_date; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/UnprotectedDatePeriod.php b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/UnprotectedDatePeriod.php new file mode 100644 index 000000000..2341be715 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/lazy/Carbon/UnprotectedDatePeriod.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DatePeriod; + +if (!class_exists(DatePeriodBase::class, false)) { + class DatePeriodBase extends DatePeriod + { + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/phpmd.yml b/data/web/inc/lib/vendor/nesbot/carbon/phpmd.yml new file mode 100644 index 000000000..f7cd51def --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/phpmd.yml @@ -0,0 +1,46 @@ +# PHPMD configuration, see https://phpmd.org/documentation/creating-a-ruleset.html +name: 'Mess detection rules for Carbon' +description: 'Mess detection rules for Carbon' +rules: + - ref: rulesets/codesize.xml + exclude: + - CyclomaticComplexity + - NPathComplexity + - ExcessiveMethodLength + - ExcessiveClassLength + - ExcessivePublicCount + - TooManyMethods + - TooManyPublicMethods + - ExcessiveClassComplexity + - ref: rulesets/cleancode.xml + exclude: + - BooleanArgumentFlag + - StaticAccess + - IfStatementAssignment + - UndefinedVariable + - ErrorControlOperator + - ref: rulesets/controversial.xml + - ref: rulesets/design.xml + exclude: + - EvalExpression + - CouplingBetweenObjects + - CountInLoopExpression + - DevelopmentCodeFragment + - ref: rulesets/design.xml/DevelopmentCodeFragment + properties: + ignore-namespaces: 'true' + - ref: rulesets/naming.xml + exclude: + - ShortVariable + - LongVariable + - ShortMethodName + - ref: rulesets/naming.xml/ShortVariable + properties: + exceptions: 'ci,id,to,tz' + - ref: rulesets/naming.xml/LongVariable + properties: + subtract-suffixes: 'Enabled,Function,Factory,Timezone,FloorEnd,DiffOptions,FromTestNow' + - ref: rulesets/naming.xml/ShortMethodName + properties: + exceptions: 'eq,gt,is,lt,ne,to,tz' + - ref: rulesets/unusedcode.xml diff --git a/data/web/inc/lib/vendor/nesbot/carbon/readme.md b/data/web/inc/lib/vendor/nesbot/carbon/readme.md index 3f4117706..1f3e09f54 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/readme.md +++ b/data/web/inc/lib/vendor/nesbot/carbon/readme.md @@ -2,11 +2,14 @@ [![Latest Stable Version](https://img.shields.io/packagist/v/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) [![Total Downloads](https://img.shields.io/packagist/dt/nesbot/carbon.svg?style=flat-square)](https://packagist.org/packages/nesbot/carbon) -[![GitHub Actions](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fbriannesbitt%2FCarbon%2Fbadge&style=flat-square&label=Build&logo=none)](https://github.com/briannesbitt/Carbon/actions) -[![codecov.io](https://img.shields.io/codecov/c/github/briannesbitt/Carbon.svg?style=flat-square)](https://codecov.io/github/briannesbitt/Carbon?branch=master) -[![Tidelift](https://tidelift.com/badges/github/briannesbitt/Carbon)](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) +[![GitHub Actions](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2FCarbonPHP%2Fcarbon%2Fbadge&style=flat-square&label=Build&logo=none)](https://github.com/CarbonPHP/carbon/actions) +[![codecov.io](https://img.shields.io/codecov/c/github/CarbonPHP/carbon.svg?style=flat-square)](https://codecov.io/github/CarbonPHP/carbon/actions?branch=master) -An international PHP extension for DateTime. [https://carbon.nesbot.com](https://carbon.nesbot.com) +An international PHP extension for DateTime. [Documentation](https://carbonphp.github.io/carbon/) + +> [!NOTE] +> We're migrating the repository from [briannesbitt/Carbon](https://github.com/briannesbitt/Carbon) to [CarbonPHP/carbon](https://github.com/CarbonPHP/carbon), +> which means if you're looking specific issues/pull-requests, you may have to search both. No other impact as code on both will be kept up to date. ```php toDateTimeString()); printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() $tomorrow = Carbon::now()->addDay(); $lastWeek = Carbon::now()->subWeek(); -$nextSummerOlympics = Carbon::createFromDate(2016)->addYears(4); $officialDate = Carbon::now()->toRfc2822String(); @@ -50,10 +52,15 @@ echo Carbon::parse('2019-07-23 14:51')->locale('fr_FR')->isoFormat('LLLL'); // ' // ... but also does 'from now', 'after' and 'before' // rolling up to seconds, minutes, hours, days, months, years -$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); -``` +$daysSinceEpoch = Carbon::createFromTimestamp(0)->diffInDays(); // something such as: + // 19817.6771 +$daysUntilInternetBlowUp = $internetWillBlowUpOn->diffInDays(); // Negative value since it's in the future: + // -5037.4560 -[Get supported nesbot/carbon with the Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme) +// Without parameter, difference is calculated from now, but doing $a->diff($b) +// it will count time from $a to $b. +Carbon::createFromTimestamp(0)->diffInDays($internetWillBlowUpOn); // 24855.1348 +``` ## Installation @@ -66,7 +73,7 @@ $ composer require nesbot/carbon ```json { "require": { - "nesbot/carbon": "^2.16" + "nesbot/carbon": "^3" } } ``` @@ -82,7 +89,7 @@ printf("Now: %s", Carbon::now()); ### Without Composer -Why are you not using [composer](https://getcomposer.org/)? Download the Carbon [latest release](https://github.com/briannesbitt/Carbon/releases) and put the contents of the ZIP archive into a directory in your project. Then require the file `autoload.php` to get all classes and dependencies loaded on need. +Why are you not using [composer](https://getcomposer.org/)? Download the Carbon [latest release](https://github.com/CarbonPHP/carbon/releases) and put the contents of the ZIP archive into a directory in your project. Then require the file `autoload.php` to get all classes and dependencies loaded on need. ```php + ### Translators -[Thanks to people helping us to translate Carbon in so many languages](https://carbon.nesbot.com/contribute/translators/) +[Thanks to people helping us to translate Carbon in so many languages](https://carbonphp.github.io/carbon/develop/translations/translators.html) ### Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. - -Онлайн казино -CasinoHex Canada -Probukmacher -Игровые автоматы -Casino-portugal.pt -Slots City -inkedin -Онлайн казино України -OnlineCasinosSpelen -Best non Gamstop sites in the UK -Real Money Pokies -Non GamStop Bookies UK -Онлайн Казино Украины -SSSTwitter -Non-GamStop Bets UK -Chudovo -UK Casino Gap -NZ Casino Deps -NonStopCasino.org -Migliori Siti Non AAMS -UK NonGamStopCasinos -SnapTik -IG Downloader -Proxidize -Blastup -AzuraCast -Triplebyte -GitHub Sponsors -Salesforce - + + + + + + + + + + + + +
Онлайн казино 777Non GamStop Bookies UKRoute4Me Route PlannerReleaf – Medizinischer Cannabis ShopPayID PokiesСтавки на спортBest non Gamstop sites in the UKCasinoHex Canada
+
See more + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OnlineCasinosSpelenTop 10 Online Casino Australia Eu ComOnline Pokies in AustraliaPaysafecard CasinoAustralian Online PokiesPayID Pokies SitesOnline Kasyno Polisparhaatuudetkasinot.comNew Casino BonusesFun88East Villa FCFun88
Fun88WNB Casino Bewertungcasino en ligne avec GirandièresJBOJBOgaia-wines.grNajlepsi BukmacherzyTop Rating casinoTideliftluckygans-casinoReal Money PokiesTransparencia en Casinos Online
Casino ohne oasisPlinko GameCasino-portugal.ptSportsbook Reviews OnlineCasinos ohne OASISPaysafecard CasinoGuidebook.BetWinnerBuitenlandse Online CasinoProbukmacherСтавки на спорт FavbetОнлайн казино casino.uainkedin
freispacebestecasinozondercruksRun WeeklyNew Casino Sites UK (2025) Help DirectTopKasynoOnline PLssddanbrown
+
-[[Become a sponsor via OpenCollective](https://opencollective.com/Carbon#sponsor)] +[[See all](https://carbonphp.github.io/carbon/#sponsors)] - - - - +[[Become a sponsor via OpenCollective*](https://opencollective.com/Carbon#sponsor)] -[[Become a sponsor via GitHub](https://github.com/sponsors/kylekatarnls)] +[[Become a sponsor via GitHub*](https://github.com/sponsors/kylekatarnls)] + +* This is a donation. No goods or services are expected in return. Any requests for refunds for those purposes will be rejected. ### Backers diff --git a/data/web/inc/lib/vendor/nesbot/carbon/sponsors.php b/data/web/inc/lib/vendor/nesbot/carbon/sponsors.php deleted file mode 100644 index 67b217161..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/sponsors.php +++ /dev/null @@ -1,129 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Carbon\CarbonImmutable; - -require_once __DIR__.'/vendor/autoload.php'; - -function getMaxHistoryMonthsByAmount($amount): int -{ - if ($amount >= 50) { - return 6; - } - - if ($amount >= 20) { - return 4; - } - - return 2; -} - -function getHtmlAttribute($rawValue): string -{ - return str_replace( - ['​', "\r"], - '', - trim(htmlspecialchars((string) $rawValue), "  \n\r\t\v\0"), - ); -} - -function getOpenCollectiveSponsors(): string -{ - $customSponsorImages = [ - // For consistency and equity among sponsors, as of now, we kindly ask our sponsors - // to provide an image having a width/height ratio between 1/1 and 2/1. - // By default, we'll show the member picture from OpenCollective, and will resize it if bigger - // int(OpenCollective.MemberId) => ImageURL - ]; - - $members = json_decode(file_get_contents('https://opencollective.com/carbon/members/all.json'), true); - - $list = array_filter($members, static function ($member): bool { - return ($member['lastTransactionAmount'] > 3 || $member['isActive']) && - $member['role'] === 'BACKER' && - $member['type'] !== 'USER' && - ( - $member['totalAmountDonated'] > 100 || - $member['lastTransactionAt'] > CarbonImmutable::now() - ->subMonthsNoOverflow(getMaxHistoryMonthsByAmount($member['lastTransactionAmount'])) - ->format('Y-m-d h:i') || - $member['isActive'] && $member['lastTransactionAmount'] >= 30 - ); - }); - - $list = array_map(static function (array $member): array { - $createdAt = CarbonImmutable::parse($member['createdAt']); - $lastTransactionAt = CarbonImmutable::parse($member['lastTransactionAt']); - - if ($createdAt->format('d H:i:s.u') > $lastTransactionAt->format('d H:i:s.u')) { - $createdAt = $createdAt - ->setDay($lastTransactionAt->day) - ->modify($lastTransactionAt->format('H:i:s.u')); - } - - $monthlyContribution = (float) ($member['totalAmountDonated'] / ceil($createdAt->floatDiffInMonths())); - - if ( - $lastTransactionAt->isAfter('last month') && - $member['lastTransactionAmount'] > $monthlyContribution - ) { - $monthlyContribution = (float) $member['lastTransactionAmount']; - } - - $yearlyContribution = (float) ($member['totalAmountDonated'] / max(1, $createdAt->floatDiffInYears())); - $status = null; - - if ($monthlyContribution > 29) { - $status = 'sponsor'; - } elseif ($monthlyContribution > 4.5 || $yearlyContribution > 29) { - $status = 'backer'; - } elseif ($member['totalAmountDonated'] > 0) { - $status = 'helper'; - } - - return array_merge($member, [ - 'star' => ($monthlyContribution > 98 || $yearlyContribution > 500), - 'status' => $status, - 'monthlyContribution' => $monthlyContribution, - 'yearlyContribution' => $yearlyContribution, - ]); - }, $list); - - usort($list, static function (array $a, array $b): int { - return ($b['monthlyContribution'] <=> $a['monthlyContribution']) - ?: ($b['totalAmountDonated'] <=> $a['totalAmountDonated']); - }); - - return implode('', array_map(static function (array $member) use ($customSponsorImages): string { - $href = htmlspecialchars($member['website'] ?? $member['profile']); - $src = $customSponsorImages[$member['MemberId'] ?? ''] ?? $member['image'] ?? (strtr($member['profile'], ['https://opencollective.com/' => 'https://images.opencollective.com/']).'/avatar/256.png'); - [$x, $y] = @getimagesize($src) ?: [0, 0]; - $validImage = ($x && $y); - $src = $validImage ? htmlspecialchars($src) : 'https://opencollective.com/static/images/default-guest-logo.svg'; - $height = $member['status'] === 'sponsor' ? 64 : 42; - $width = min($height * 2, $validImage ? round($x * $height / $y) : $height); - $href .= (strpos($href, '?') === false ? '?' : '&').'utm_source=opencollective&utm_medium=github&utm_campaign=Carbon'; - $title = getHtmlAttribute(($member['description'] ?? null) ?: $member['name']); - $alt = getHtmlAttribute($member['name']); - - return "\n".''. - ''.$alt.''. - ''; - }, $list))."\n"; -} - -file_put_contents('readme.md', preg_replace_callback( - '/()[\s\S]+()/', - static function (array $match): string { - return $match[1].getOpenCollectiveSponsors().$match[2]; - }, - file_get_contents('readme.md') -)); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php index 8b8fe089e..a372a1ab6 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/AbstractTranslator.php @@ -1,5 +1,7 @@ + */ + protected array $fileCache = []; /** * Set to true while constructing. - * - * @var bool */ - protected $initializing = false; + protected bool $initializing = false; /** * List of locales aliases. * * @var array */ - protected $aliases = [ + protected array $aliases = [ 'me' => 'sr_Latn_ME', 'scr' => 'sh', ]; @@ -66,32 +77,31 @@ abstract class AbstractTranslator extends Translation\Translator * * @return static */ - public static function get($locale = null) + public static function get(?string $locale = null): static { $locale = $locale ?: 'en'; $key = static::class === Translator::class ? $locale : static::class.'|'.$locale; + $count = \count(static::$singletons); - if (!isset(static::$singletons[$key])) { - static::$singletons[$key] = new static($locale); + // Remember only the last 10 translators created + if ($count > 10) { + foreach (\array_slice(array_keys(static::$singletons), 0, $count - 10) as $index) { + unset(static::$singletons[$index]); + } } + static::$singletons[$key] ??= new static($locale); + return static::$singletons[$key]; } - public function __construct($locale, MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) + public function __construct($locale, ?MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false) { - parent::setLocale($locale); - $this->initializing = true; - $this->directories = [__DIR__.'/Lang']; - $this->addLoader('array', new ArrayLoader()); - parent::__construct($locale, new MessageFormatterMapper($formatter), $cacheDir, $debug); - $this->initializing = false; + $this->initialize($locale, $formatter, $cacheDir, $debug); } /** * Returns the list of directories translation files are searched in. - * - * @return array */ public function getDirectories(): array { @@ -105,7 +115,7 @@ abstract class AbstractTranslator extends Translation\Translator * * @return $this */ - public function setDirectories(array $directories) + public function setDirectories(array $directories): static { $this->directories = $directories; @@ -119,7 +129,7 @@ abstract class AbstractTranslator extends Translation\Translator * * @return $this */ - public function addDirectory(string $directory) + public function addDirectory(string $directory): static { $this->directories[] = $directory; @@ -133,37 +143,51 @@ abstract class AbstractTranslator extends Translation\Translator * * @return $this */ - public function removeDirectory(string $directory) + public function removeDirectory(string $directory): static { $search = rtrim(strtr($directory, '\\', '/'), '/'); - return $this->setDirectories(array_filter($this->getDirectories(), function ($item) use ($search) { - return rtrim(strtr($item, '\\', '/'), '/') !== $search; - })); + return $this->setDirectories(array_filter( + $this->getDirectories(), + static fn ($item) => rtrim(strtr($item, '\\', '/'), '/') !== $search, + )); } /** * Reset messages of a locale (all locale if no locale passed). * Remove custom messages and reload initial messages from matching * file in Lang directory. - * - * @param string|null $locale - * - * @return bool */ - public function resetMessages($locale = null) + public function resetMessages(?string $locale = null): bool { if ($locale === null) { $this->messages = []; + $this->catalogues = []; + $this->modifyResources(static function (array $resources): array { + foreach ($resources as &$list) { + array_splice($list, 1); + } + + return $resources; + }); return true; } + $this->assertValidLocale($locale); + foreach ($this->getDirectories() as $directory) { - $data = @include sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale); + $file = \sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale); + $data = ($this->fileCache[$file] ??= self::loadFile($file)); if ($data !== false) { $this->messages[$locale] = $data; + unset($this->catalogues[$locale]); + $this->modifyResources(static function (array $resources) use ($locale): array { + unset($resources[$locale]); + + return $resources; + }); $this->addResource('array', $this->messages[$locale], $locale); return true; @@ -180,14 +204,12 @@ abstract class AbstractTranslator extends Translation\Translator * * @return array */ - public function getLocalesFiles($prefix = '') + public function getLocalesFiles(string $prefix = ''): array { $files = []; foreach ($this->getDirectories() as $directory) { - $directory = rtrim($directory, '\\/'); - - foreach (glob("$directory/$prefix*.php") as $file) { + foreach (self::getPhpFilesInDirectory(rtrim($directory, '\\/'), $prefix) as $file) { $files[] = $file; } } @@ -203,22 +225,20 @@ abstract class AbstractTranslator extends Translation\Translator * * @return array */ - public function getAvailableLocales($prefix = '') + public function getAvailableLocales(string $prefix = ''): array { - $locales = []; - foreach ($this->getLocalesFiles($prefix) as $file) { - $locales[] = substr($file, strrpos($file, '/') + 1, -4); - } - - return array_unique(array_merge($locales, array_keys($this->messages))); + return array_unique(array_merge( + array_map( + static fn (string $file) => substr($file, strrpos($file, '/') + 1, -4), + $this->getLocalesFiles($prefix), + ), + array_keys($this->messages), + )); } protected function translate(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { - if ($domain === null) { - $domain = 'messages'; - } - + $domain ??= 'messages'; $catalogue = $this->getCatalogue($locale); $format = $this instanceof TranslatorStrongTypeInterface ? $this->getFromCatalogue($catalogue, (string) $id, $domain) @@ -228,7 +248,7 @@ abstract class AbstractTranslator extends Translation\Translator // @codeCoverageIgnoreStart try { $count = (new ReflectionFunction($format))->getNumberOfRequiredParameters(); - } catch (ReflectionException $exception) { + } catch (ReflectionException) { $count = 0; } // @codeCoverageIgnoreEnd @@ -249,7 +269,7 @@ abstract class AbstractTranslator extends Translation\Translator * * @return bool */ - protected function loadMessagesFromFile($locale) + protected function loadMessagesFromFile(string $locale): bool { return isset($this->messages[$locale]) || $this->resetMessages($locale); } @@ -262,7 +282,7 @@ abstract class AbstractTranslator extends Translation\Translator * * @return $this */ - public function setMessages($locale, $messages) + public function setMessages(string $locale, array $messages): static { $this->loadMessagesFromFile($locale); $this->addResource('array', $messages, $locale); @@ -281,7 +301,7 @@ abstract class AbstractTranslator extends Translation\Translator * * @return $this */ - public function setTranslations($messages) + public function setTranslations(array $messages): static { return $this->setMessages($this->getLocale(), $messages); } @@ -289,40 +309,38 @@ abstract class AbstractTranslator extends Translation\Translator /** * Get messages of a locale, if none given, return all the * languages. - * - * @param string|null $locale - * - * @return array */ - public function getMessages($locale = null) + public function getMessages(?string $locale = null): array { - return $locale === null ? $this->messages : $this->messages[$locale]; + return $locale === null ? $this->messages : ($this->messages[$locale] ?? []); } /** * Set the current translator locale and indicate if the source locale file exists * * @param string $locale locale ex. en - * - * @return bool */ - public function setLocale($locale) + public function setLocale($locale): void { + $previousLocale = $this->getLocale(); + + if ($previousLocale === $locale && isset($this->messages[$locale])) { + return; + } + $locale = preg_replace_callback('/[-_]([a-z]{2,}|\d{2,})/', function ($matches) { // _2-letters or YUE is a region, _3+-letters is a variant $upper = strtoupper($matches[1]); - if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) < 3) { + if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) <= static::REGION_CODE_LENGTH) { return "_$upper"; } return '_'.ucfirst($matches[1]); }, strtolower($locale)); - $previousLocale = $this->getLocale(); - if ($previousLocale === $locale && isset($this->messages[$locale])) { - return true; + return; } unset(static::$singletons[$previousLocale]); @@ -334,35 +352,36 @@ abstract class AbstractTranslator extends Translation\Translator $completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale); - $getScore = function ($language) use ($completeLocaleChunks) { - return self::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language)); - }; + $getScore = static fn ($language) => self::compareChunkLists( + $completeLocaleChunks, + preg_split('/[_.-]+/', $language), + ); - usort($locales, function ($first, $second) use ($getScore) { - return $getScore($second) <=> $getScore($first); - }); + usort($locales, static fn ($first, $second) => $getScore($second) <=> $getScore($first)); - $locale = $locales[0]; + $locale = $locales[0] ?? 'en'; } if (isset($this->aliases[$locale])) { $locale = $this->aliases[$locale]; } - // If subtag (ex: en_CA) first load the macro (ex: en) to have a fallback - if (str_contains($locale, '_') && - $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale)) + // If the language is not provided by a Carbon file + // and the tag contains a region (ex: en_CA), then + // first load the macro (ex: en) to have a fallback + if ( + str_contains($locale, '_') + && !\in_array($locale, self::getInternallySupportedLocales(), true) + && $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale)) ) { parent::setLocale($macroLocale); } if (!$this->loadMessagesFromFile($locale) && !$this->initializing) { - return false; + return; } parent::setLocale($locale); - - return true; } /** @@ -377,7 +396,34 @@ abstract class AbstractTranslator extends Translation\Translator ]; } - private static function compareChunkLists($referenceChunks, $chunks) + public function __serialize(): array + { + return [ + 'locale' => $this->getLocale(), + ]; + } + + public function __unserialize(array $data): void + { + $this->initialize($data['locale'] ?? 'en'); + } + + private function initialize($locale, ?MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false): void + { + parent::setLocale($locale); + $this->initializing = true; + $this->directories = [self::getDefaultLangDirectory()]; + $this->addLoader('array', new ArrayLoader()); + parent::__construct($locale, new MessageFormatterMapper($formatter), $cacheDir, $debug); + $this->initializing = false; + } + + private function loadFile(string $file): array|false + { + return file_exists($file) ? (include $file) : false; + } + + private static function compareChunkLists(array $referenceChunks, array $chunks): int { $score = 0; @@ -395,4 +441,875 @@ abstract class AbstractTranslator extends Translation\Translator return $score; } + + /** @codeCoverageIgnore */ + private function modifyResources(callable $callback): void + { + try { + $resourcesProperty = new ReflectionProperty(SymfonyTranslator::class, 'resources'); + $resources = $resourcesProperty->getValue($this); + $resourcesProperty->setValue($this, $callback($resources)); + } catch (Throwable) { + // Clear resources if available, if not, then nothing to clean + } + } + + private static function getPhpFilesInDirectory(string $directory, string $prefix): array + { + if ($directory !== self::getDefaultLangDirectory()) { + return glob("$directory/$prefix*.php") ?: []; + } + + // If it's the internal Carbon directory we use a static list + // which is faster than scanning the folder with glob() + $locales = self::getInternallySupportedLocales(); + + if ($prefix !== '') { + $locales = array_values(array_filter( + self::getInternallySupportedLocales(), + static fn (string $locale) => str_starts_with($locale, $prefix), + )); + } + + return array_map( + static fn (string $locale) => "$directory/$locale.php", + $locales, + ); + } + + private static function getDefaultLangDirectory(): string + { + return __DIR__.'/Lang'; + } + + /** @return list */ + private static function getInternallySupportedLocales(): array + { + return [ + 'aa', + 'aa_DJ', + 'aa_ER', + 'aa_ER@saaho', + 'aa_ET', + 'af', + 'af_NA', + 'af_ZA', + 'agq', + 'agr', + 'agr_PE', + 'ak', + 'ak_GH', + 'am', + 'am_ET', + 'an', + 'an_ES', + 'anp', + 'anp_IN', + 'ar', + 'ar_AE', + 'ar_BH', + 'ar_DJ', + 'ar_DZ', + 'ar_EG', + 'ar_EH', + 'ar_ER', + 'ar_IL', + 'ar_IN', + 'ar_IQ', + 'ar_JO', + 'ar_KM', + 'ar_KW', + 'ar_LB', + 'ar_LY', + 'ar_MA', + 'ar_MR', + 'ar_OM', + 'ar_PS', + 'ar_QA', + 'ar_SA', + 'ar_SD', + 'ar_SO', + 'ar_SS', + 'ar_SY', + 'ar_Shakl', + 'ar_TD', + 'ar_TN', + 'ar_YE', + 'as', + 'as_IN', + 'asa', + 'ast', + 'ast_ES', + 'ayc', + 'ayc_PE', + 'az', + 'az_AZ', + 'az_Arab', + 'az_Cyrl', + 'az_IR', + 'az_Latn', + 'bas', + 'be', + 'be_BY', + 'be_BY@latin', + 'bem', + 'bem_ZM', + 'ber', + 'ber_DZ', + 'ber_MA', + 'bez', + 'bg', + 'bg_BG', + 'bhb', + 'bhb_IN', + 'bho', + 'bho_IN', + 'bi', + 'bi_VU', + 'bm', + 'bn', + 'bn_BD', + 'bn_IN', + 'bo', + 'bo_CN', + 'bo_IN', + 'br', + 'br_FR', + 'brx', + 'brx_IN', + 'bs', + 'bs_BA', + 'bs_Cyrl', + 'bs_Latn', + 'byn', + 'byn_ER', + 'ca', + 'ca_AD', + 'ca_ES', + 'ca_ES_Valencia', + 'ca_FR', + 'ca_IT', + 'ccp', + 'ccp_IN', + 'ce', + 'ce_RU', + 'cgg', + 'chr', + 'chr_US', + 'ckb', + 'cmn', + 'cmn_TW', + 'crh', + 'crh_UA', + 'cs', + 'cs_CZ', + 'csb', + 'csb_PL', + 'cu', + 'cv', + 'cv_RU', + 'cy', + 'cy_GB', + 'da', + 'da_DK', + 'da_GL', + 'dav', + 'de', + 'de_AT', + 'de_BE', + 'de_CH', + 'de_DE', + 'de_IT', + 'de_LI', + 'de_LU', + 'dje', + 'doi', + 'doi_IN', + 'dsb', + 'dsb_DE', + 'dua', + 'dv', + 'dv_MV', + 'dyo', + 'dz', + 'dz_BT', + 'ebu', + 'ee', + 'ee_TG', + 'el', + 'el_CY', + 'el_GR', + 'en', + 'en_001', + 'en_150', + 'en_AG', + 'en_AI', + 'en_AS', + 'en_AT', + 'en_AU', + 'en_BB', + 'en_BE', + 'en_BI', + 'en_BM', + 'en_BS', + 'en_BW', + 'en_BZ', + 'en_CA', + 'en_CC', + 'en_CH', + 'en_CK', + 'en_CM', + 'en_CX', + 'en_CY', + 'en_DE', + 'en_DG', + 'en_DK', + 'en_DM', + 'en_ER', + 'en_FI', + 'en_FJ', + 'en_FK', + 'en_FM', + 'en_GB', + 'en_GD', + 'en_GG', + 'en_GH', + 'en_GI', + 'en_GM', + 'en_GU', + 'en_GY', + 'en_HK', + 'en_IE', + 'en_IL', + 'en_IM', + 'en_IN', + 'en_IO', + 'en_ISO', + 'en_JE', + 'en_JM', + 'en_KE', + 'en_KI', + 'en_KN', + 'en_KY', + 'en_LC', + 'en_LR', + 'en_LS', + 'en_MG', + 'en_MH', + 'en_MO', + 'en_MP', + 'en_MS', + 'en_MT', + 'en_MU', + 'en_MW', + 'en_MY', + 'en_NA', + 'en_NF', + 'en_NG', + 'en_NL', + 'en_NR', + 'en_NU', + 'en_NZ', + 'en_PG', + 'en_PH', + 'en_PK', + 'en_PN', + 'en_PR', + 'en_PW', + 'en_RW', + 'en_SB', + 'en_SC', + 'en_SD', + 'en_SE', + 'en_SG', + 'en_SH', + 'en_SI', + 'en_SL', + 'en_SS', + 'en_SX', + 'en_SZ', + 'en_TC', + 'en_TK', + 'en_TO', + 'en_TT', + 'en_TV', + 'en_TZ', + 'en_UG', + 'en_UM', + 'en_US', + 'en_US_Posix', + 'en_VC', + 'en_VG', + 'en_VI', + 'en_VU', + 'en_WS', + 'en_ZA', + 'en_ZM', + 'en_ZW', + 'eo', + 'es', + 'es_419', + 'es_AR', + 'es_BO', + 'es_BR', + 'es_BZ', + 'es_CL', + 'es_CO', + 'es_CR', + 'es_CU', + 'es_DO', + 'es_EA', + 'es_EC', + 'es_ES', + 'es_GQ', + 'es_GT', + 'es_HN', + 'es_IC', + 'es_MX', + 'es_NI', + 'es_PA', + 'es_PE', + 'es_PH', + 'es_PR', + 'es_PY', + 'es_SV', + 'es_US', + 'es_UY', + 'es_VE', + 'et', + 'et_EE', + 'eu', + 'eu_ES', + 'ewo', + 'fa', + 'fa_AF', + 'fa_IR', + 'ff', + 'ff_CM', + 'ff_GN', + 'ff_MR', + 'ff_SN', + 'fi', + 'fi_FI', + 'fil', + 'fil_PH', + 'fo', + 'fo_DK', + 'fo_FO', + 'fr', + 'fr_BE', + 'fr_BF', + 'fr_BI', + 'fr_BJ', + 'fr_BL', + 'fr_CA', + 'fr_CD', + 'fr_CF', + 'fr_CG', + 'fr_CH', + 'fr_CI', + 'fr_CM', + 'fr_DJ', + 'fr_DZ', + 'fr_FR', + 'fr_GA', + 'fr_GF', + 'fr_GN', + 'fr_GP', + 'fr_GQ', + 'fr_HT', + 'fr_KM', + 'fr_LU', + 'fr_MA', + 'fr_MC', + 'fr_MF', + 'fr_MG', + 'fr_ML', + 'fr_MQ', + 'fr_MR', + 'fr_MU', + 'fr_NC', + 'fr_NE', + 'fr_PF', + 'fr_PM', + 'fr_RE', + 'fr_RW', + 'fr_SC', + 'fr_SN', + 'fr_SY', + 'fr_TD', + 'fr_TG', + 'fr_TN', + 'fr_VU', + 'fr_WF', + 'fr_YT', + 'fur', + 'fur_IT', + 'fy', + 'fy_DE', + 'fy_NL', + 'ga', + 'ga_IE', + 'gd', + 'gd_GB', + 'gez', + 'gez_ER', + 'gez_ET', + 'gl', + 'gl_ES', + 'gom', + 'gom_Latn', + 'gsw', + 'gsw_CH', + 'gsw_FR', + 'gsw_LI', + 'gu', + 'gu_IN', + 'guz', + 'gv', + 'gv_GB', + 'ha', + 'ha_GH', + 'ha_NE', + 'ha_NG', + 'hak', + 'hak_TW', + 'haw', + 'he', + 'he_IL', + 'hi', + 'hi_IN', + 'hif', + 'hif_FJ', + 'hne', + 'hne_IN', + 'hr', + 'hr_BA', + 'hr_HR', + 'hsb', + 'hsb_DE', + 'ht', + 'ht_HT', + 'hu', + 'hu_HU', + 'hy', + 'hy_AM', + 'i18n', + 'ia', + 'ia_FR', + 'id', + 'id_ID', + 'ig', + 'ig_NG', + 'ii', + 'ik', + 'ik_CA', + 'in', + 'is', + 'is_IS', + 'it', + 'it_CH', + 'it_IT', + 'it_SM', + 'it_VA', + 'iu', + 'iu_CA', + 'iw', + 'ja', + 'ja_JP', + 'jgo', + 'jmc', + 'jv', + 'ka', + 'ka_GE', + 'kab', + 'kab_DZ', + 'kam', + 'kde', + 'kea', + 'khq', + 'ki', + 'kk', + 'kk_KZ', + 'kkj', + 'kl', + 'kl_GL', + 'kln', + 'km', + 'km_KH', + 'kn', + 'kn_IN', + 'ko', + 'ko_KP', + 'ko_KR', + 'kok', + 'kok_IN', + 'ks', + 'ks_IN', + 'ks_IN@devanagari', + 'ksb', + 'ksf', + 'ksh', + 'ku', + 'ku_TR', + 'kw', + 'kw_GB', + 'ky', + 'ky_KG', + 'lag', + 'lb', + 'lb_LU', + 'lg', + 'lg_UG', + 'li', + 'li_NL', + 'lij', + 'lij_IT', + 'lkt', + 'ln', + 'ln_AO', + 'ln_CD', + 'ln_CF', + 'ln_CG', + 'lo', + 'lo_LA', + 'lrc', + 'lrc_IQ', + 'lt', + 'lt_LT', + 'lu', + 'luo', + 'luy', + 'lv', + 'lv_LV', + 'lzh', + 'lzh_TW', + 'mag', + 'mag_IN', + 'mai', + 'mai_IN', + 'mas', + 'mas_TZ', + 'mer', + 'mfe', + 'mfe_MU', + 'mg', + 'mg_MG', + 'mgh', + 'mgo', + 'mhr', + 'mhr_RU', + 'mi', + 'mi_NZ', + 'miq', + 'miq_NI', + 'mjw', + 'mjw_IN', + 'mk', + 'mk_MK', + 'ml', + 'ml_IN', + 'mn', + 'mn_MN', + 'mni', + 'mni_IN', + 'mo', + 'mr', + 'mr_IN', + 'ms', + 'ms_BN', + 'ms_MY', + 'ms_SG', + 'mt', + 'mt_MT', + 'mua', + 'my', + 'my_MM', + 'mzn', + 'nan', + 'nan_TW', + 'nan_TW@latin', + 'naq', + 'nb', + 'nb_NO', + 'nb_SJ', + 'nd', + 'nds', + 'nds_DE', + 'nds_NL', + 'ne', + 'ne_IN', + 'ne_NP', + 'nhn', + 'nhn_MX', + 'niu', + 'niu_NU', + 'nl', + 'nl_AW', + 'nl_BE', + 'nl_BQ', + 'nl_CW', + 'nl_NL', + 'nl_SR', + 'nl_SX', + 'nmg', + 'nn', + 'nn_NO', + 'nnh', + 'no', + 'nr', + 'nr_ZA', + 'nso', + 'nso_ZA', + 'nus', + 'nyn', + 'oc', + 'oc_FR', + 'om', + 'om_ET', + 'om_KE', + 'or', + 'or_IN', + 'os', + 'os_RU', + 'pa', + 'pa_Arab', + 'pa_Guru', + 'pa_IN', + 'pa_PK', + 'pap', + 'pap_AW', + 'pap_CW', + 'pl', + 'pl_PL', + 'prg', + 'ps', + 'ps_AF', + 'pt', + 'pt_AO', + 'pt_BR', + 'pt_CH', + 'pt_CV', + 'pt_GQ', + 'pt_GW', + 'pt_LU', + 'pt_MO', + 'pt_MZ', + 'pt_PT', + 'pt_ST', + 'pt_TL', + 'qu', + 'qu_BO', + 'qu_EC', + 'quz', + 'quz_PE', + 'raj', + 'raj_IN', + 'rm', + 'rn', + 'ro', + 'ro_MD', + 'ro_RO', + 'rof', + 'ru', + 'ru_BY', + 'ru_KG', + 'ru_KZ', + 'ru_MD', + 'ru_RU', + 'ru_UA', + 'rw', + 'rw_RW', + 'rwk', + 'sa', + 'sa_IN', + 'sah', + 'sah_RU', + 'saq', + 'sat', + 'sat_IN', + 'sbp', + 'sc', + 'sc_IT', + 'sd', + 'sd_IN', + 'sd_IN@devanagari', + 'se', + 'se_FI', + 'se_NO', + 'se_SE', + 'seh', + 'ses', + 'sg', + 'sgs', + 'sgs_LT', + 'sh', + 'shi', + 'shi_Latn', + 'shi_Tfng', + 'shn', + 'shn_MM', + 'shs', + 'shs_CA', + 'si', + 'si_LK', + 'sid', + 'sid_ET', + 'sk', + 'sk_SK', + 'sl', + 'sl_SI', + 'sm', + 'sm_WS', + 'smn', + 'sn', + 'so', + 'so_DJ', + 'so_ET', + 'so_KE', + 'so_SO', + 'sq', + 'sq_AL', + 'sq_MK', + 'sq_XK', + 'sr', + 'sr_Cyrl', + 'sr_Cyrl_BA', + 'sr_Cyrl_ME', + 'sr_Cyrl_XK', + 'sr_Latn', + 'sr_Latn_BA', + 'sr_Latn_ME', + 'sr_Latn_XK', + 'sr_ME', + 'sr_RS', + 'sr_RS@latin', + 'ss', + 'ss_ZA', + 'st', + 'st_ZA', + 'sv', + 'sv_AX', + 'sv_FI', + 'sv_SE', + 'sw', + 'sw_CD', + 'sw_KE', + 'sw_TZ', + 'sw_UG', + 'szl', + 'szl_PL', + 'ta', + 'ta_IN', + 'ta_LK', + 'ta_MY', + 'ta_SG', + 'tcy', + 'tcy_IN', + 'te', + 'te_IN', + 'teo', + 'teo_KE', + 'tet', + 'tg', + 'tg_TJ', + 'th', + 'th_TH', + 'the', + 'the_NP', + 'ti', + 'ti_ER', + 'ti_ET', + 'tig', + 'tig_ER', + 'tk', + 'tk_TM', + 'tl', + 'tl_PH', + 'tlh', + 'tn', + 'tn_ZA', + 'to', + 'to_TO', + 'tpi', + 'tpi_PG', + 'tr', + 'tr_CY', + 'tr_TR', + 'ts', + 'ts_ZA', + 'tt', + 'tt_RU', + 'tt_RU@iqtelif', + 'twq', + 'tzl', + 'tzm', + 'tzm_Latn', + 'ug', + 'ug_CN', + 'uk', + 'uk_UA', + 'unm', + 'unm_US', + 'ur', + 'ur_IN', + 'ur_PK', + 'uz', + 'uz_Arab', + 'uz_Cyrl', + 'uz_Latn', + 'uz_UZ', + 'uz_UZ@cyrillic', + 'vai', + 'vai_Latn', + 'vai_Vaii', + 've', + 've_ZA', + 'vi', + 'vi_VN', + 'vo', + 'vun', + 'wa', + 'wa_BE', + 'wae', + 'wae_CH', + 'wal', + 'wal_ET', + 'wo', + 'wo_SN', + 'xh', + 'xh_ZA', + 'xog', + 'yav', + 'yi', + 'yi_US', + 'yo', + 'yo_BJ', + 'yo_NG', + 'yue', + 'yue_HK', + 'yue_Hans', + 'yue_Hant', + 'yuw', + 'yuw_PG', + 'zgh', + 'zh', + 'zh_CN', + 'zh_HK', + 'zh_Hans', + 'zh_Hans_HK', + 'zh_Hans_MO', + 'zh_Hans_SG', + 'zh_Hant', + 'zh_Hant_HK', + 'zh_Hant_MO', + 'zh_Hant_TW', + 'zh_MO', + 'zh_SG', + 'zh_TW', + 'zh_YUE', + 'zu', + 'zu_ZA', + ]; + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Callback.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Callback.php new file mode 100644 index 000000000..c7c12cd60 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Callback.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Closure; +use DateInterval; +use DatePeriod; +use DateTime; +use DateTimeInterface; +use DateTimeZone; +use ReflectionFunction; +use ReflectionNamedType; +use ReflectionType; +use Time\Duration; + +final class Callback +{ + private ?ReflectionFunction $function; + + private function __construct(private readonly Closure $closure) + { + } + + public static function fromClosure(Closure $closure): self + { + return new self($closure); + } + + public static function parameter(mixed $closure, mixed $value, string|int $index = 0): mixed + { + if ($closure instanceof Closure) { + return self::fromClosure($closure)->prepareParameter($value, $index); + } + + return $value; + } + + public function getReflectionFunction(): ReflectionFunction + { + return $this->function ??= new ReflectionFunction($this->closure); + } + + public function prepareParameter(mixed $value, string|int $index = 0): mixed + { + $type = $this->getParameterType($index); + + if (!($type instanceof ReflectionNamedType)) { + return $value; + } + + $name = $type->getName(); + + if ($name === CarbonInterface::class) { + $name = $value instanceof DateTime ? Carbon::class : CarbonImmutable::class; + } + + if (!class_exists($name) || is_a($value, $name)) { + return $value; + } + + $class = $this->getPromotedClass($value); + + if ($class && is_a($name, $class, true)) { + return $name::instance($value); + } + + if ($name === Duration::class && $value instanceof DateInterval) { + return DurationConverter::createDurationFromInterval($value); + } + + return $value; + } + + public function call(mixed ...$arguments): mixed + { + foreach ($arguments as $index => &$value) { + if ($this->getPromotedClass($value)) { + $value = $this->prepareParameter($value, $index); + } + } + + return ($this->closure)(...$arguments); + } + + private function getParameterType(string|int $index): ?ReflectionType + { + $parameters = $this->getReflectionFunction()->getParameters(); + + if (\is_int($index)) { + return ($parameters[$index] ?? null)?->getType(); + } + + foreach ($parameters as $parameter) { + if ($parameter->getName() === $index) { + return $parameter->getType(); + } + } + + return null; + } + + /** @return class-string|null */ + private function getPromotedClass(mixed $value): ?string + { + if ($value instanceof DateTimeInterface) { + return CarbonInterface::class; + } + + if ($value instanceof DateInterval) { + return CarbonInterval::class; + } + + if ($value instanceof DatePeriod) { + return CarbonPeriod::class; + } + + if ($value instanceof DateTimeZone) { + return CarbonTimeZone::class; + } + + return null; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php index e32569ae3..d8b2bef72 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Carbon.php @@ -1,5 +1,7 @@ * - * @property int $year - * @property int $yearIso - * @property int $month - * @property int $day - * @property int $hour - * @property int $minute - * @property int $second - * @property int $micro - * @property int $microsecond - * @property int|float|string $timestamp seconds since the Unix Epoch - * @property string $englishDayOfWeek the day of week in English - * @property string $shortEnglishDayOfWeek the abbreviated day of week in English - * @property string $englishMonth the month in English - * @property string $shortEnglishMonth the abbreviated month in English - * @property int $milliseconds - * @property int $millisecond - * @property int $milli - * @property int $week 1 through 53 - * @property int $isoWeek 1 through 53 - * @property int $weekYear year according to week format - * @property int $isoWeekYear year according to ISO week format - * @property int $dayOfYear 1 through 366 - * @property int $age does a diffInYears() with default parameters - * @property int $offset the timezone offset in seconds from UTC - * @property int $offsetMinutes the timezone offset in minutes from UTC - * @property int $offsetHours the timezone offset in hours from UTC - * @property CarbonTimeZone $timezone the current timezone - * @property CarbonTimeZone $tz alias of $timezone - * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) - * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) - * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday - * @property-read int $daysInMonth number of days in the given month - * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) - * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) - * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name - * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName - * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read int $noZeroHour current hour from 1 to 24 - * @property-read int $weeksInYear 51 through 53 - * @property-read int $isoWeeksInYear 51 through 53 - * @property-read int $weekOfMonth 1 through 5 - * @property-read int $weekNumberInMonth 1 through 5 - * @property-read int $firstWeekDay 0 through 6 - * @property-read int $lastWeekDay 0 through 6 - * @property-read int $daysInYear 365 or 366 - * @property-read int $quarter the quarter of this instance, 1 - 4 - * @property-read int $decade the decade of this instance - * @property-read int $century the century of this instance - * @property-read int $millennium the millennium of this instance - * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise - * @property-read bool $local checks if the timezone is local, true if local, false otherwise - * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise - * @property-read string $timezoneName the current timezone name - * @property-read string $tzName alias of $timezoneName - * @property-read string $locale locale of the current instance + * @property string $localeDayOfWeek the day of week in current locale + * @property string $shortLocaleDayOfWeek the abbreviated day of week in current locale + * @property string $localeMonth the month in current locale + * @property string $shortLocaleMonth the abbreviated month in current locale + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property int $centuryOfMillennium The value of the century starting from the beginning of the current millennium + * @property int $dayOfCentury The value of the day starting from the beginning of the current century + * @property int $dayOfDecade The value of the day starting from the beginning of the current decade + * @property int $dayOfMillennium The value of the day starting from the beginning of the current millennium + * @property int $dayOfMonth The value of the day starting from the beginning of the current month + * @property int $dayOfQuarter The value of the day starting from the beginning of the current quarter + * @property int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property int $dayOfYear 1 through 366 + * @property int $decadeOfCentury The value of the decade starting from the beginning of the current century + * @property int $decadeOfMillennium The value of the decade starting from the beginning of the current millennium + * @property int $hourOfCentury The value of the hour starting from the beginning of the current century + * @property int $hourOfDay The value of the hour starting from the beginning of the current day + * @property int $hourOfDecade The value of the hour starting from the beginning of the current decade + * @property int $hourOfMillennium The value of the hour starting from the beginning of the current millennium + * @property int $hourOfMonth The value of the hour starting from the beginning of the current month + * @property int $hourOfQuarter The value of the hour starting from the beginning of the current quarter + * @property int $hourOfWeek The value of the hour starting from the beginning of the current week + * @property int $hourOfYear The value of the hour starting from the beginning of the current year + * @property int $microsecondOfCentury The value of the microsecond starting from the beginning of the current century + * @property int $microsecondOfDay The value of the microsecond starting from the beginning of the current day + * @property int $microsecondOfDecade The value of the microsecond starting from the beginning of the current decade + * @property int $microsecondOfHour The value of the microsecond starting from the beginning of the current hour + * @property int $microsecondOfMillennium The value of the microsecond starting from the beginning of the current millennium + * @property int $microsecondOfMillisecond The value of the microsecond starting from the beginning of the current millisecond + * @property int $microsecondOfMinute The value of the microsecond starting from the beginning of the current minute + * @property int $microsecondOfMonth The value of the microsecond starting from the beginning of the current month + * @property int $microsecondOfQuarter The value of the microsecond starting from the beginning of the current quarter + * @property int $microsecondOfSecond The value of the microsecond starting from the beginning of the current second + * @property int $microsecondOfWeek The value of the microsecond starting from the beginning of the current week + * @property int $microsecondOfYear The value of the microsecond starting from the beginning of the current year + * @property int $millisecondOfCentury The value of the millisecond starting from the beginning of the current century + * @property int $millisecondOfDay The value of the millisecond starting from the beginning of the current day + * @property int $millisecondOfDecade The value of the millisecond starting from the beginning of the current decade + * @property int $millisecondOfHour The value of the millisecond starting from the beginning of the current hour + * @property int $millisecondOfMillennium The value of the millisecond starting from the beginning of the current millennium + * @property int $millisecondOfMinute The value of the millisecond starting from the beginning of the current minute + * @property int $millisecondOfMonth The value of the millisecond starting from the beginning of the current month + * @property int $millisecondOfQuarter The value of the millisecond starting from the beginning of the current quarter + * @property int $millisecondOfSecond The value of the millisecond starting from the beginning of the current second + * @property int $millisecondOfWeek The value of the millisecond starting from the beginning of the current week + * @property int $millisecondOfYear The value of the millisecond starting from the beginning of the current year + * @property int $minuteOfCentury The value of the minute starting from the beginning of the current century + * @property int $minuteOfDay The value of the minute starting from the beginning of the current day + * @property int $minuteOfDecade The value of the minute starting from the beginning of the current decade + * @property int $minuteOfHour The value of the minute starting from the beginning of the current hour + * @property int $minuteOfMillennium The value of the minute starting from the beginning of the current millennium + * @property int $minuteOfMonth The value of the minute starting from the beginning of the current month + * @property int $minuteOfQuarter The value of the minute starting from the beginning of the current quarter + * @property int $minuteOfWeek The value of the minute starting from the beginning of the current week + * @property int $minuteOfYear The value of the minute starting from the beginning of the current year + * @property int $monthOfCentury The value of the month starting from the beginning of the current century + * @property int $monthOfDecade The value of the month starting from the beginning of the current decade + * @property int $monthOfMillennium The value of the month starting from the beginning of the current millennium + * @property int $monthOfQuarter The value of the month starting from the beginning of the current quarter + * @property int $monthOfYear The value of the month starting from the beginning of the current year + * @property int $quarterOfCentury The value of the quarter starting from the beginning of the current century + * @property int $quarterOfDecade The value of the quarter starting from the beginning of the current decade + * @property int $quarterOfMillennium The value of the quarter starting from the beginning of the current millennium + * @property int $quarterOfYear The value of the quarter starting from the beginning of the current year + * @property int $secondOfCentury The value of the second starting from the beginning of the current century + * @property int $secondOfDay The value of the second starting from the beginning of the current day + * @property int $secondOfDecade The value of the second starting from the beginning of the current decade + * @property int $secondOfHour The value of the second starting from the beginning of the current hour + * @property int $secondOfMillennium The value of the second starting from the beginning of the current millennium + * @property int $secondOfMinute The value of the second starting from the beginning of the current minute + * @property int $secondOfMonth The value of the second starting from the beginning of the current month + * @property int $secondOfQuarter The value of the second starting from the beginning of the current quarter + * @property int $secondOfWeek The value of the second starting from the beginning of the current week + * @property int $secondOfYear The value of the second starting from the beginning of the current year + * @property int $weekOfCentury The value of the week starting from the beginning of the current century + * @property int $weekOfDecade The value of the week starting from the beginning of the current decade + * @property int $weekOfMillennium The value of the week starting from the beginning of the current millennium + * @property int $weekOfMonth 1 through 5 + * @property int $weekOfQuarter The value of the week starting from the beginning of the current quarter + * @property int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property int $yearOfCentury The value of the year starting from the beginning of the current century + * @property int $yearOfDecade The value of the year starting from the beginning of the current decade + * @property int $yearOfMillennium The value of the year starting from the beginning of the current millennium + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * @property-read int $centuriesInMillennium The number of centuries contained in the current millennium + * @property-read int $daysInCentury The number of days contained in the current century + * @property-read int $daysInDecade The number of days contained in the current decade + * @property-read int $daysInMillennium The number of days contained in the current millennium + * @property-read int $daysInMonth number of days in the given month + * @property-read int $daysInQuarter The number of days contained in the current quarter + * @property-read int $daysInWeek The number of days contained in the current week + * @property-read int $daysInYear 365 or 366 + * @property-read int $decadesInCentury The number of decades contained in the current century + * @property-read int $decadesInMillennium The number of decades contained in the current millennium + * @property-read int $hoursInCentury The number of hours contained in the current century + * @property-read int $hoursInDay The number of hours contained in the current day + * @property-read int $hoursInDecade The number of hours contained in the current decade + * @property-read int $hoursInMillennium The number of hours contained in the current millennium + * @property-read int $hoursInMonth The number of hours contained in the current month + * @property-read int $hoursInQuarter The number of hours contained in the current quarter + * @property-read int $hoursInWeek The number of hours contained in the current week + * @property-read int $hoursInYear The number of hours contained in the current year + * @property-read int $microsecondsInCentury The number of microseconds contained in the current century + * @property-read int $microsecondsInDay The number of microseconds contained in the current day + * @property-read int $microsecondsInDecade The number of microseconds contained in the current decade + * @property-read int $microsecondsInHour The number of microseconds contained in the current hour + * @property-read int $microsecondsInMillennium The number of microseconds contained in the current millennium + * @property-read int $microsecondsInMillisecond The number of microseconds contained in the current millisecond + * @property-read int $microsecondsInMinute The number of microseconds contained in the current minute + * @property-read int $microsecondsInMonth The number of microseconds contained in the current month + * @property-read int $microsecondsInQuarter The number of microseconds contained in the current quarter + * @property-read int $microsecondsInSecond The number of microseconds contained in the current second + * @property-read int $microsecondsInWeek The number of microseconds contained in the current week + * @property-read int $microsecondsInYear The number of microseconds contained in the current year + * @property-read int $millisecondsInCentury The number of milliseconds contained in the current century + * @property-read int $millisecondsInDay The number of milliseconds contained in the current day + * @property-read int $millisecondsInDecade The number of milliseconds contained in the current decade + * @property-read int $millisecondsInHour The number of milliseconds contained in the current hour + * @property-read int $millisecondsInMillennium The number of milliseconds contained in the current millennium + * @property-read int $millisecondsInMinute The number of milliseconds contained in the current minute + * @property-read int $millisecondsInMonth The number of milliseconds contained in the current month + * @property-read int $millisecondsInQuarter The number of milliseconds contained in the current quarter + * @property-read int $millisecondsInSecond The number of milliseconds contained in the current second + * @property-read int $millisecondsInWeek The number of milliseconds contained in the current week + * @property-read int $millisecondsInYear The number of milliseconds contained in the current year + * @property-read int $minutesInCentury The number of minutes contained in the current century + * @property-read int $minutesInDay The number of minutes contained in the current day + * @property-read int $minutesInDecade The number of minutes contained in the current decade + * @property-read int $minutesInHour The number of minutes contained in the current hour + * @property-read int $minutesInMillennium The number of minutes contained in the current millennium + * @property-read int $minutesInMonth The number of minutes contained in the current month + * @property-read int $minutesInQuarter The number of minutes contained in the current quarter + * @property-read int $minutesInWeek The number of minutes contained in the current week + * @property-read int $minutesInYear The number of minutes contained in the current year + * @property-read int $monthsInCentury The number of months contained in the current century + * @property-read int $monthsInDecade The number of months contained in the current decade + * @property-read int $monthsInMillennium The number of months contained in the current millennium + * @property-read int $monthsInQuarter The number of months contained in the current quarter + * @property-read int $monthsInYear The number of months contained in the current year + * @property-read int $quartersInCentury The number of quarters contained in the current century + * @property-read int $quartersInDecade The number of quarters contained in the current decade + * @property-read int $quartersInMillennium The number of quarters contained in the current millennium + * @property-read int $quartersInYear The number of quarters contained in the current year + * @property-read int $secondsInCentury The number of seconds contained in the current century + * @property-read int $secondsInDay The number of seconds contained in the current day + * @property-read int $secondsInDecade The number of seconds contained in the current decade + * @property-read int $secondsInHour The number of seconds contained in the current hour + * @property-read int $secondsInMillennium The number of seconds contained in the current millennium + * @property-read int $secondsInMinute The number of seconds contained in the current minute + * @property-read int $secondsInMonth The number of seconds contained in the current month + * @property-read int $secondsInQuarter The number of seconds contained in the current quarter + * @property-read int $secondsInWeek The number of seconds contained in the current week + * @property-read int $secondsInYear The number of seconds contained in the current year + * @property-read int $weeksInCentury The number of weeks contained in the current century + * @property-read int $weeksInDecade The number of weeks contained in the current decade + * @property-read int $weeksInMillennium The number of weeks contained in the current millennium + * @property-read int $weeksInMonth The number of weeks contained in the current month + * @property-read int $weeksInQuarter The number of weeks contained in the current quarter + * @property-read int $weeksInYear 51 through 53 + * @property-read int $yearsInCentury The number of years contained in the current century + * @property-read int $yearsInDecade The number of years contained in the current decade + * @property-read int $yearsInMillennium The number of years contained in the current millennium * - * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) - * @method bool isLocal() Check if the current instance has non-UTC timezone. - * @method bool isValid() Check if the current instance is a valid date. - * @method bool isDST() Check if the current instance is in a daylight saving time. - * @method bool isSunday() Checks if the instance day is sunday. - * @method bool isMonday() Checks if the instance day is monday. - * @method bool isTuesday() Checks if the instance day is tuesday. - * @method bool isWednesday() Checks if the instance day is wednesday. - * @method bool isThursday() Checks if the instance day is thursday. - * @method bool isFriday() Checks if the instance day is friday. - * @method bool isSaturday() Checks if the instance day is saturday. - * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. - * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. - * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. - * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. - * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. - * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. - * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. - * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. - * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. - * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. - * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. - * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. - * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. - * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. - * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. - * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. - * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. - * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. - * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. - * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. - * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. - * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. - * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. - * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. - * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. - * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. - * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. - * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. - * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. - * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. - * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. - * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. - * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. - * @method $this years(int $value) Set current instance year to the given value. - * @method $this year(int $value) Set current instance year to the given value. - * @method $this setYears(int $value) Set current instance year to the given value. - * @method $this setYear(int $value) Set current instance year to the given value. - * @method $this months(int $value) Set current instance month to the given value. - * @method $this month(int $value) Set current instance month to the given value. - * @method $this setMonths(int $value) Set current instance month to the given value. - * @method $this setMonth(int $value) Set current instance month to the given value. - * @method $this days(int $value) Set current instance day to the given value. - * @method $this day(int $value) Set current instance day to the given value. - * @method $this setDays(int $value) Set current instance day to the given value. - * @method $this setDay(int $value) Set current instance day to the given value. - * @method $this hours(int $value) Set current instance hour to the given value. - * @method $this hour(int $value) Set current instance hour to the given value. - * @method $this setHours(int $value) Set current instance hour to the given value. - * @method $this setHour(int $value) Set current instance hour to the given value. - * @method $this minutes(int $value) Set current instance minute to the given value. - * @method $this minute(int $value) Set current instance minute to the given value. - * @method $this setMinutes(int $value) Set current instance minute to the given value. - * @method $this setMinute(int $value) Set current instance minute to the given value. - * @method $this seconds(int $value) Set current instance second to the given value. - * @method $this second(int $value) Set current instance second to the given value. - * @method $this setSeconds(int $value) Set current instance second to the given value. - * @method $this setSecond(int $value) Set current instance second to the given value. - * @method $this millis(int $value) Set current instance millisecond to the given value. - * @method $this milli(int $value) Set current instance millisecond to the given value. - * @method $this setMillis(int $value) Set current instance millisecond to the given value. - * @method $this setMilli(int $value) Set current instance millisecond to the given value. - * @method $this milliseconds(int $value) Set current instance millisecond to the given value. - * @method $this millisecond(int $value) Set current instance millisecond to the given value. - * @method $this setMilliseconds(int $value) Set current instance millisecond to the given value. - * @method $this setMillisecond(int $value) Set current instance millisecond to the given value. - * @method $this micros(int $value) Set current instance microsecond to the given value. - * @method $this micro(int $value) Set current instance microsecond to the given value. - * @method $this setMicros(int $value) Set current instance microsecond to the given value. - * @method $this setMicro(int $value) Set current instance microsecond to the given value. - * @method $this microseconds(int $value) Set current instance microsecond to the given value. - * @method $this microsecond(int $value) Set current instance microsecond to the given value. - * @method $this setMicroseconds(int $value) Set current instance microsecond to the given value. - * @method $this setMicrosecond(int $value) Set current instance microsecond to the given value. - * @method $this addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). - * @method $this addYear() Add one year to the instance (using date interval). - * @method $this subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). - * @method $this subYear() Sub one year to the instance (using date interval). - * @method $this addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. - * @method $this subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. - * @method $this addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). - * @method $this addMonth() Add one month to the instance (using date interval). - * @method $this subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). - * @method $this subMonth() Sub one month to the instance (using date interval). - * @method $this addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. - * @method $this subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. - * @method $this addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). - * @method $this addDay() Add one day to the instance (using date interval). - * @method $this subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). - * @method $this subDay() Sub one day to the instance (using date interval). - * @method $this addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). - * @method $this addHour() Add one hour to the instance (using date interval). - * @method $this subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). - * @method $this subHour() Sub one hour to the instance (using date interval). - * @method $this addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). - * @method $this addMinute() Add one minute to the instance (using date interval). - * @method $this subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). - * @method $this subMinute() Sub one minute to the instance (using date interval). - * @method $this addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). - * @method $this addSecond() Add one second to the instance (using date interval). - * @method $this subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). - * @method $this subSecond() Sub one second to the instance (using date interval). - * @method $this addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method $this addMilli() Add one millisecond to the instance (using date interval). - * @method $this subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method $this subMilli() Sub one millisecond to the instance (using date interval). - * @method $this addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method $this addMillisecond() Add one millisecond to the instance (using date interval). - * @method $this subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method $this subMillisecond() Sub one millisecond to the instance (using date interval). - * @method $this addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method $this addMicro() Add one microsecond to the instance (using date interval). - * @method $this subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method $this subMicro() Sub one microsecond to the instance (using date interval). - * @method $this addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method $this addMicrosecond() Add one microsecond to the instance (using date interval). - * @method $this subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method $this subMicrosecond() Sub one microsecond to the instance (using date interval). - * @method $this addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). - * @method $this addMillennium() Add one millennium to the instance (using date interval). - * @method $this subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). - * @method $this subMillennium() Sub one millennium to the instance (using date interval). - * @method $this addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method $this subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method $this addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). - * @method $this addCentury() Add one century to the instance (using date interval). - * @method $this subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). - * @method $this subCentury() Sub one century to the instance (using date interval). - * @method $this addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. - * @method $this subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. - * @method $this addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). - * @method $this addDecade() Add one decade to the instance (using date interval). - * @method $this subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). - * @method $this subDecade() Sub one decade to the instance (using date interval). - * @method $this addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. - * @method $this subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. - * @method $this addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). - * @method $this addQuarter() Add one quarter to the instance (using date interval). - * @method $this subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). - * @method $this subQuarter() Sub one quarter to the instance (using date interval). - * @method $this addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method $this subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method $this subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method $this addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method $this subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method $this addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). - * @method $this addWeek() Add one week to the instance (using date interval). - * @method $this subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). - * @method $this subWeek() Sub one week to the instance (using date interval). - * @method $this addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). - * @method $this addWeekday() Add one weekday to the instance (using date interval). - * @method $this subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). - * @method $this subWeekday() Sub one weekday to the instance (using date interval). - * @method $this addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMicro() Add one microsecond to the instance (using timestamp). - * @method $this subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMicro() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method $this addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMicrosecond() Add one microsecond to the instance (using timestamp). - * @method $this subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMicrosecond() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method $this addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMilli() Add one millisecond to the instance (using timestamp). - * @method $this subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMilli() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method $this addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMillisecond() Add one millisecond to the instance (using timestamp). - * @method $this subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMillisecond() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method $this addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). - * @method $this addRealSecond() Add one second to the instance (using timestamp). - * @method $this subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). - * @method $this subRealSecond() Sub one second to the instance (using timestamp). - * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. - * @method $this addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMinute() Add one minute to the instance (using timestamp). - * @method $this subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMinute() Sub one minute to the instance (using timestamp). - * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. - * @method $this addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). - * @method $this addRealHour() Add one hour to the instance (using timestamp). - * @method $this subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). - * @method $this subRealHour() Sub one hour to the instance (using timestamp). - * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. - * @method $this addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). - * @method $this addRealDay() Add one day to the instance (using timestamp). - * @method $this subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). - * @method $this subRealDay() Sub one day to the instance (using timestamp). - * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. - * @method $this addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). - * @method $this addRealWeek() Add one week to the instance (using timestamp). - * @method $this subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). - * @method $this subRealWeek() Sub one week to the instance (using timestamp). - * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. - * @method $this addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMonth() Add one month to the instance (using timestamp). - * @method $this subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMonth() Sub one month to the instance (using timestamp). - * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. - * @method $this addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). - * @method $this addRealQuarter() Add one quarter to the instance (using timestamp). - * @method $this subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). - * @method $this subRealQuarter() Sub one quarter to the instance (using timestamp). - * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. - * @method $this addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). - * @method $this addRealYear() Add one year to the instance (using timestamp). - * @method $this subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). - * @method $this subRealYear() Sub one year to the instance (using timestamp). - * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. - * @method $this addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). - * @method $this addRealDecade() Add one decade to the instance (using timestamp). - * @method $this subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). - * @method $this subRealDecade() Sub one decade to the instance (using timestamp). - * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. - * @method $this addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). - * @method $this addRealCentury() Add one century to the instance (using timestamp). - * @method $this subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). - * @method $this subRealCentury() Sub one century to the instance (using timestamp). - * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. - * @method $this addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). - * @method $this addRealMillennium() Add one millennium to the instance (using timestamp). - * @method $this subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). - * @method $this subRealMillennium() Sub one millennium to the instance (using timestamp). - * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. - * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. - * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. - * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. - * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. - * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. - * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. - * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. - * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. - * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. - * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. - * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. - * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. - * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. - * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. - * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. - * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. - * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. - * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. - * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. - * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. - * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. - * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. - * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. - * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. - * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. - * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. - * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. - * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. - * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. - * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. - * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. - * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. - * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. - * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. - * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. - * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. - * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. - * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. - * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. - * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. - * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new Carbon object according to the specified format. - * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(DateTimeInterface|string $date) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isSameWeek(DateTimeInterface|string $date) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(DateTimeInterface|string $date) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(DateTimeInterface|string $date) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(DateTimeInterface|string $date) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(DateTimeInterface|string $date) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMilli(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMilli() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMilli() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMilli() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMillisecond(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillisecond() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMillisecond() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMillisecond() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMicro(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameDecade(DateTimeInterface|string $date) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(DateTimeInterface|string $date) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(DateTimeInterface|string $date) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method $this years(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method $this year(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method $this setYears(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method $this setYear(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method $this months(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method $this month(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method $this setMonths(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method $this setMonth(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method $this days(int $value) Set current instance day to the given value. + * @method $this day(int $value) Set current instance day to the given value. + * @method $this setDays(int $value) Set current instance day to the given value. + * @method $this setDay(int $value) Set current instance day to the given value. + * @method $this hours(int $value) Set current instance hour to the given value. + * @method $this hour(int $value) Set current instance hour to the given value. + * @method $this setHours(int $value) Set current instance hour to the given value. + * @method $this setHour(int $value) Set current instance hour to the given value. + * @method $this minutes(int $value) Set current instance minute to the given value. + * @method $this minute(int $value) Set current instance minute to the given value. + * @method $this setMinutes(int $value) Set current instance minute to the given value. + * @method $this setMinute(int $value) Set current instance minute to the given value. + * @method $this seconds(int $value) Set current instance second to the given value. + * @method $this second(int $value) Set current instance second to the given value. + * @method $this setSeconds(int $value) Set current instance second to the given value. + * @method $this setSecond(int $value) Set current instance second to the given value. + * @method $this millis(int $value) Set current instance millisecond to the given value. + * @method $this milli(int $value) Set current instance millisecond to the given value. + * @method $this setMillis(int $value) Set current instance millisecond to the given value. + * @method $this setMilli(int $value) Set current instance millisecond to the given value. + * @method $this milliseconds(int $value) Set current instance millisecond to the given value. + * @method $this millisecond(int $value) Set current instance millisecond to the given value. + * @method $this setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method $this setMillisecond(int $value) Set current instance millisecond to the given value. + * @method $this micros(int $value) Set current instance microsecond to the given value. + * @method $this micro(int $value) Set current instance microsecond to the given value. + * @method $this setMicros(int $value) Set current instance microsecond to the given value. + * @method $this setMicro(int $value) Set current instance microsecond to the given value. + * @method $this microseconds(int $value) Set current instance microsecond to the given value. + * @method $this microsecond(int $value) Set current instance microsecond to the given value. + * @method $this setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method self setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method $this addYears(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval). + * @method $this addYear() Add one year to the instance (using date interval). + * @method $this subYears(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval). + * @method $this subYear() Sub one year to the instance (using date interval). + * @method $this addYearsWithOverflow(int|float $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearsWithOverflow(int|float $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method $this addYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonths(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval). + * @method $this addMonth() Add one month to the instance (using date interval). + * @method $this subMonths(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval). + * @method $this subMonth() Sub one month to the instance (using date interval). + * @method $this addMonthsWithOverflow(int|float $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthsWithOverflow(int|float $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method $this addDay() Add one day to the instance (using date interval). + * @method $this subDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method $this subDay() Sub one day to the instance (using date interval). + * @method $this addHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method $this addHour() Add one hour to the instance (using date interval). + * @method $this subHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method $this subHour() Sub one hour to the instance (using date interval). + * @method $this addMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method $this addMinute() Add one minute to the instance (using date interval). + * @method $this subMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method $this subMinute() Sub one minute to the instance (using date interval). + * @method $this addSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method $this addSecond() Add one second to the instance (using date interval). + * @method $this subSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method $this subSecond() Sub one second to the instance (using date interval). + * @method $this addMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMilli() Add one millisecond to the instance (using date interval). + * @method $this subMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMilli() Sub one millisecond to the instance (using date interval). + * @method $this addMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMillisecond() Add one millisecond to the instance (using date interval). + * @method $this subMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMillisecond() Sub one millisecond to the instance (using date interval). + * @method $this addMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicro() Add one microsecond to the instance (using date interval). + * @method $this subMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicro() Sub one microsecond to the instance (using date interval). + * @method $this addMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method $this addMicrosecond() Add one microsecond to the instance (using date interval). + * @method $this subMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method $this subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method $this addMillennia(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval). + * @method $this addMillennium() Add one millennium to the instance (using date interval). + * @method $this subMillennia(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method $this subMillennium() Sub one millennium to the instance (using date interval). + * @method $this addMillenniaWithOverflow(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniaWithOverflow(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method $this addMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturies(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval). + * @method $this addCentury() Add one century to the instance (using date interval). + * @method $this subCenturies(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method $this subCentury() Sub one century to the instance (using date interval). + * @method $this addCenturiesWithOverflow(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturiesWithOverflow(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method $this addCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecades(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval). + * @method $this addDecade() Add one decade to the instance (using date interval). + * @method $this subDecades(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval). + * @method $this subDecade() Sub one decade to the instance (using date interval). + * @method $this addDecadesWithOverflow(int|float $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadesWithOverflow(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method $this addDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarters(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval). + * @method $this addQuarter() Add one quarter to the instance (using date interval). + * @method $this subQuarters(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method $this subQuarter() Sub one quarter to the instance (using date interval). + * @method $this addQuartersWithOverflow(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuartersWithOverflow(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method $this subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method $this addQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method $this subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method $this addWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method $this addWeek() Add one week to the instance (using date interval). + * @method $this subWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method $this subWeek() Sub one week to the instance (using date interval). + * @method $this addWeekdays(int|float $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method $this addWeekday() Add one weekday to the instance (using date interval). + * @method $this subWeekdays(int|float $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method $this subWeekday() Sub one weekday to the instance (using date interval). + * @method $this addUTCMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMicro() Add one microsecond to the instance (using timestamp). + * @method $this subUTCMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicros(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method $this addUTCMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMicrosecond() Add one microsecond to the instance (using timestamp). + * @method $this subUTCMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicroseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method $this addUTCMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMilli() Add one millisecond to the instance (using timestamp). + * @method $this subUTCMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMillis(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method $this addUTCMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMillisecond() Add one millisecond to the instance (using timestamp). + * @method $this subUTCMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMilliseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method $this addUTCSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCSecond() Add one second to the instance (using timestamp). + * @method $this subUTCSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method float diffInUTCSeconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of seconds. + * @method $this addUTCMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMinute() Add one minute to the instance (using timestamp). + * @method $this subUTCMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method float diffInUTCMinutes(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of minutes. + * @method $this addUTCHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCHour() Add one hour to the instance (using timestamp). + * @method $this subUTCHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method float diffInUTCHours(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of hours. + * @method $this addUTCDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCDay() Add one day to the instance (using timestamp). + * @method $this subUTCDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method float diffInUTCDays(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of days. + * @method $this addUTCWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCWeek() Add one week to the instance (using timestamp). + * @method $this subUTCWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method float diffInUTCWeeks(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of weeks. + * @method $this addUTCMonths(int|float $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMonth() Add one month to the instance (using timestamp). + * @method $this subUTCMonths(int|float $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method float diffInUTCMonths(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of months. + * @method $this addUTCQuarters(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCQuarter() Add one quarter to the instance (using timestamp). + * @method $this subUTCQuarters(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method float diffInUTCQuarters(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of quarters. + * @method $this addUTCYears(int|float $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCYear() Add one year to the instance (using timestamp). + * @method $this subUTCYears(int|float $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method float diffInUTCYears(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of years. + * @method $this addUTCDecades(int|float $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCDecade() Add one decade to the instance (using timestamp). + * @method $this subUTCDecades(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method float diffInUTCDecades(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of decades. + * @method $this addUTCCenturies(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCCentury() Add one century to the instance (using timestamp). + * @method $this subUTCCenturies(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method float diffInUTCCenturies(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of centuries. + * @method $this addUTCMillennia(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method $this addUTCMillennium() Add one millennium to the instance (using timestamp). + * @method $this subUTCMillennia(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method $this subUTCMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method float diffInUTCMillennia(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of millennia. + * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method int centuriesInMillennium() Return the number of centuries contained in the current millennium + * @method int|static centuryOfMillennium(?int $century = null) Return the value of the century starting from the beginning of the current millennium when called with no parameters, change the current century when called with an integer value + * @method int|static dayOfCentury(?int $day = null) Return the value of the day starting from the beginning of the current century when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfDecade(?int $day = null) Return the value of the day starting from the beginning of the current decade when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMillennium(?int $day = null) Return the value of the day starting from the beginning of the current millennium when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMonth(?int $day = null) Return the value of the day starting from the beginning of the current month when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfQuarter(?int $day = null) Return the value of the day starting from the beginning of the current quarter when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfWeek(?int $day = null) Return the value of the day starting from the beginning of the current week when called with no parameters, change the current day when called with an integer value + * @method int daysInCentury() Return the number of days contained in the current century + * @method int daysInDecade() Return the number of days contained in the current decade + * @method int daysInMillennium() Return the number of days contained in the current millennium + * @method int daysInMonth() Return the number of days contained in the current month + * @method int daysInQuarter() Return the number of days contained in the current quarter + * @method int daysInWeek() Return the number of days contained in the current week + * @method int daysInYear() Return the number of days contained in the current year + * @method int|static decadeOfCentury(?int $decade = null) Return the value of the decade starting from the beginning of the current century when called with no parameters, change the current decade when called with an integer value + * @method int|static decadeOfMillennium(?int $decade = null) Return the value of the decade starting from the beginning of the current millennium when called with no parameters, change the current decade when called with an integer value + * @method int decadesInCentury() Return the number of decades contained in the current century + * @method int decadesInMillennium() Return the number of decades contained in the current millennium + * @method int|static hourOfCentury(?int $hour = null) Return the value of the hour starting from the beginning of the current century when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDay(?int $hour = null) Return the value of the hour starting from the beginning of the current day when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDecade(?int $hour = null) Return the value of the hour starting from the beginning of the current decade when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMillennium(?int $hour = null) Return the value of the hour starting from the beginning of the current millennium when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMonth(?int $hour = null) Return the value of the hour starting from the beginning of the current month when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfQuarter(?int $hour = null) Return the value of the hour starting from the beginning of the current quarter when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfWeek(?int $hour = null) Return the value of the hour starting from the beginning of the current week when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfYear(?int $hour = null) Return the value of the hour starting from the beginning of the current year when called with no parameters, change the current hour when called with an integer value + * @method int hoursInCentury() Return the number of hours contained in the current century + * @method int hoursInDay() Return the number of hours contained in the current day + * @method int hoursInDecade() Return the number of hours contained in the current decade + * @method int hoursInMillennium() Return the number of hours contained in the current millennium + * @method int hoursInMonth() Return the number of hours contained in the current month + * @method int hoursInQuarter() Return the number of hours contained in the current quarter + * @method int hoursInWeek() Return the number of hours contained in the current week + * @method int hoursInYear() Return the number of hours contained in the current year + * @method int|static microsecondOfCentury(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current century when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDay(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current day when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDecade(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current decade when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfHour(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current hour when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillennium(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millennium when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillisecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millisecond when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMinute(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current minute when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMonth(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current month when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfQuarter(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current quarter when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfSecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current second when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfWeek(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current week when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfYear(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current year when called with no parameters, change the current microsecond when called with an integer value + * @method int microsecondsInCentury() Return the number of microseconds contained in the current century + * @method int microsecondsInDay() Return the number of microseconds contained in the current day + * @method int microsecondsInDecade() Return the number of microseconds contained in the current decade + * @method int microsecondsInHour() Return the number of microseconds contained in the current hour + * @method int microsecondsInMillennium() Return the number of microseconds contained in the current millennium + * @method int microsecondsInMillisecond() Return the number of microseconds contained in the current millisecond + * @method int microsecondsInMinute() Return the number of microseconds contained in the current minute + * @method int microsecondsInMonth() Return the number of microseconds contained in the current month + * @method int microsecondsInQuarter() Return the number of microseconds contained in the current quarter + * @method int microsecondsInSecond() Return the number of microseconds contained in the current second + * @method int microsecondsInWeek() Return the number of microseconds contained in the current week + * @method int microsecondsInYear() Return the number of microseconds contained in the current year + * @method int|static millisecondOfCentury(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current century when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDay(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current day when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDecade(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current decade when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfHour(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current hour when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMillennium(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current millennium when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMinute(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current minute when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMonth(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current month when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfQuarter(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current quarter when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfSecond(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current second when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfWeek(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current week when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfYear(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current year when called with no parameters, change the current millisecond when called with an integer value + * @method int millisecondsInCentury() Return the number of milliseconds contained in the current century + * @method int millisecondsInDay() Return the number of milliseconds contained in the current day + * @method int millisecondsInDecade() Return the number of milliseconds contained in the current decade + * @method int millisecondsInHour() Return the number of milliseconds contained in the current hour + * @method int millisecondsInMillennium() Return the number of milliseconds contained in the current millennium + * @method int millisecondsInMinute() Return the number of milliseconds contained in the current minute + * @method int millisecondsInMonth() Return the number of milliseconds contained in the current month + * @method int millisecondsInQuarter() Return the number of milliseconds contained in the current quarter + * @method int millisecondsInSecond() Return the number of milliseconds contained in the current second + * @method int millisecondsInWeek() Return the number of milliseconds contained in the current week + * @method int millisecondsInYear() Return the number of milliseconds contained in the current year + * @method int|static minuteOfCentury(?int $minute = null) Return the value of the minute starting from the beginning of the current century when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDay(?int $minute = null) Return the value of the minute starting from the beginning of the current day when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDecade(?int $minute = null) Return the value of the minute starting from the beginning of the current decade when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfHour(?int $minute = null) Return the value of the minute starting from the beginning of the current hour when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMillennium(?int $minute = null) Return the value of the minute starting from the beginning of the current millennium when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMonth(?int $minute = null) Return the value of the minute starting from the beginning of the current month when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfQuarter(?int $minute = null) Return the value of the minute starting from the beginning of the current quarter when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfWeek(?int $minute = null) Return the value of the minute starting from the beginning of the current week when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfYear(?int $minute = null) Return the value of the minute starting from the beginning of the current year when called with no parameters, change the current minute when called with an integer value + * @method int minutesInCentury() Return the number of minutes contained in the current century + * @method int minutesInDay() Return the number of minutes contained in the current day + * @method int minutesInDecade() Return the number of minutes contained in the current decade + * @method int minutesInHour() Return the number of minutes contained in the current hour + * @method int minutesInMillennium() Return the number of minutes contained in the current millennium + * @method int minutesInMonth() Return the number of minutes contained in the current month + * @method int minutesInQuarter() Return the number of minutes contained in the current quarter + * @method int minutesInWeek() Return the number of minutes contained in the current week + * @method int minutesInYear() Return the number of minutes contained in the current year + * @method int|static monthOfCentury(?int $month = null) Return the value of the month starting from the beginning of the current century when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfDecade(?int $month = null) Return the value of the month starting from the beginning of the current decade when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfMillennium(?int $month = null) Return the value of the month starting from the beginning of the current millennium when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfQuarter(?int $month = null) Return the value of the month starting from the beginning of the current quarter when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfYear(?int $month = null) Return the value of the month starting from the beginning of the current year when called with no parameters, change the current month when called with an integer value + * @method int monthsInCentury() Return the number of months contained in the current century + * @method int monthsInDecade() Return the number of months contained in the current decade + * @method int monthsInMillennium() Return the number of months contained in the current millennium + * @method int monthsInQuarter() Return the number of months contained in the current quarter + * @method int monthsInYear() Return the number of months contained in the current year + * @method int|static quarterOfCentury(?int $quarter = null) Return the value of the quarter starting from the beginning of the current century when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfDecade(?int $quarter = null) Return the value of the quarter starting from the beginning of the current decade when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfMillennium(?int $quarter = null) Return the value of the quarter starting from the beginning of the current millennium when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfYear(?int $quarter = null) Return the value of the quarter starting from the beginning of the current year when called with no parameters, change the current quarter when called with an integer value + * @method int quartersInCentury() Return the number of quarters contained in the current century + * @method int quartersInDecade() Return the number of quarters contained in the current decade + * @method int quartersInMillennium() Return the number of quarters contained in the current millennium + * @method int quartersInYear() Return the number of quarters contained in the current year + * @method int|static secondOfCentury(?int $second = null) Return the value of the second starting from the beginning of the current century when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDay(?int $second = null) Return the value of the second starting from the beginning of the current day when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDecade(?int $second = null) Return the value of the second starting from the beginning of the current decade when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfHour(?int $second = null) Return the value of the second starting from the beginning of the current hour when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMillennium(?int $second = null) Return the value of the second starting from the beginning of the current millennium when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMinute(?int $second = null) Return the value of the second starting from the beginning of the current minute when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMonth(?int $second = null) Return the value of the second starting from the beginning of the current month when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfQuarter(?int $second = null) Return the value of the second starting from the beginning of the current quarter when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfWeek(?int $second = null) Return the value of the second starting from the beginning of the current week when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfYear(?int $second = null) Return the value of the second starting from the beginning of the current year when called with no parameters, change the current second when called with an integer value + * @method int secondsInCentury() Return the number of seconds contained in the current century + * @method int secondsInDay() Return the number of seconds contained in the current day + * @method int secondsInDecade() Return the number of seconds contained in the current decade + * @method int secondsInHour() Return the number of seconds contained in the current hour + * @method int secondsInMillennium() Return the number of seconds contained in the current millennium + * @method int secondsInMinute() Return the number of seconds contained in the current minute + * @method int secondsInMonth() Return the number of seconds contained in the current month + * @method int secondsInQuarter() Return the number of seconds contained in the current quarter + * @method int secondsInWeek() Return the number of seconds contained in the current week + * @method int secondsInYear() Return the number of seconds contained in the current year + * @method int|static weekOfCentury(?int $week = null) Return the value of the week starting from the beginning of the current century when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfDecade(?int $week = null) Return the value of the week starting from the beginning of the current decade when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMillennium(?int $week = null) Return the value of the week starting from the beginning of the current millennium when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMonth(?int $week = null) Return the value of the week starting from the beginning of the current month when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfQuarter(?int $week = null) Return the value of the week starting from the beginning of the current quarter when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfYear(?int $week = null) Return the value of the week starting from the beginning of the current year when called with no parameters, change the current week when called with an integer value + * @method int weeksInCentury() Return the number of weeks contained in the current century + * @method int weeksInDecade() Return the number of weeks contained in the current decade + * @method int weeksInMillennium() Return the number of weeks contained in the current millennium + * @method int weeksInMonth() Return the number of weeks contained in the current month + * @method int weeksInQuarter() Return the number of weeks contained in the current quarter + * @method int|static yearOfCentury(?int $year = null) Return the value of the year starting from the beginning of the current century when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfDecade(?int $year = null) Return the value of the year starting from the beginning of the current decade when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfMillennium(?int $year = null) Return the value of the year starting from the beginning of the current millennium when called with no parameters, change the current year when called with an integer value + * @method int yearsInCentury() Return the number of years contained in the current century + * @method int yearsInDecade() Return the number of years contained in the current decade + * @method int yearsInMillennium() Return the number of years contained in the current millennium + * @method static plus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Add given amount of time to the current date. + * @method static minus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Subtract given amount of time to the current date. * * */ @@ -513,10 +841,8 @@ class Carbon extends DateTime implements CarbonInterface /** * Returns true if the current class/instance is mutable. - * - * @return bool */ - public static function isMutable() + public static function isMutable(): bool { return true; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php index 1ce967b25..fd89bd776 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonConverterInterface.php @@ -1,5 +1,7 @@ * - * @property int $year - * @property int $yearIso - * @property int $month - * @property int $day - * @property int $hour - * @property int $minute - * @property int $second - * @property int $micro - * @property int $microsecond - * @property int|float|string $timestamp seconds since the Unix Epoch - * @property string $englishDayOfWeek the day of week in English - * @property string $shortEnglishDayOfWeek the abbreviated day of week in English - * @property string $englishMonth the month in English - * @property string $shortEnglishMonth the abbreviated month in English - * @property int $milliseconds - * @property int $millisecond - * @property int $milli - * @property int $week 1 through 53 - * @property int $isoWeek 1 through 53 - * @property int $weekYear year according to week format - * @property int $isoWeekYear year according to ISO week format - * @property int $dayOfYear 1 through 366 - * @property int $age does a diffInYears() with default parameters - * @property int $offset the timezone offset in seconds from UTC - * @property int $offsetMinutes the timezone offset in minutes from UTC - * @property int $offsetHours the timezone offset in hours from UTC - * @property CarbonTimeZone $timezone the current timezone - * @property CarbonTimeZone $tz alias of $timezone - * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) - * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) - * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday - * @property-read int $daysInMonth number of days in the given month - * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) - * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) - * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name - * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName - * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read int $noZeroHour current hour from 1 to 24 - * @property-read int $weeksInYear 51 through 53 - * @property-read int $isoWeeksInYear 51 through 53 - * @property-read int $weekOfMonth 1 through 5 - * @property-read int $weekNumberInMonth 1 through 5 - * @property-read int $firstWeekDay 0 through 6 - * @property-read int $lastWeekDay 0 through 6 - * @property-read int $daysInYear 365 or 366 - * @property-read int $quarter the quarter of this instance, 1 - 4 - * @property-read int $decade the decade of this instance - * @property-read int $century the century of this instance - * @property-read int $millennium the millennium of this instance - * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise - * @property-read bool $local checks if the timezone is local, true if local, false otherwise - * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise - * @property-read string $timezoneName the current timezone name - * @property-read string $tzName alias of $timezoneName - * @property-read string $locale locale of the current instance + * @property string $localeDayOfWeek the day of week in current locale + * @property string $shortLocaleDayOfWeek the abbreviated day of week in current locale + * @property string $localeMonth the month in current locale + * @property string $shortLocaleMonth the abbreviated month in current locale + * @property int $year + * @property int $yearIso + * @property int $month + * @property int $day + * @property int $hour + * @property int $minute + * @property int $second + * @property int $micro + * @property int $microsecond + * @property int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English + * @property int $milliseconds + * @property int $millisecond + * @property int $milli + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property int $centuryOfMillennium The value of the century starting from the beginning of the current millennium + * @property int $dayOfCentury The value of the day starting from the beginning of the current century + * @property int $dayOfDecade The value of the day starting from the beginning of the current decade + * @property int $dayOfMillennium The value of the day starting from the beginning of the current millennium + * @property int $dayOfMonth The value of the day starting from the beginning of the current month + * @property int $dayOfQuarter The value of the day starting from the beginning of the current quarter + * @property int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property int $dayOfYear 1 through 366 + * @property int $decadeOfCentury The value of the decade starting from the beginning of the current century + * @property int $decadeOfMillennium The value of the decade starting from the beginning of the current millennium + * @property int $hourOfCentury The value of the hour starting from the beginning of the current century + * @property int $hourOfDay The value of the hour starting from the beginning of the current day + * @property int $hourOfDecade The value of the hour starting from the beginning of the current decade + * @property int $hourOfMillennium The value of the hour starting from the beginning of the current millennium + * @property int $hourOfMonth The value of the hour starting from the beginning of the current month + * @property int $hourOfQuarter The value of the hour starting from the beginning of the current quarter + * @property int $hourOfWeek The value of the hour starting from the beginning of the current week + * @property int $hourOfYear The value of the hour starting from the beginning of the current year + * @property int $microsecondOfCentury The value of the microsecond starting from the beginning of the current century + * @property int $microsecondOfDay The value of the microsecond starting from the beginning of the current day + * @property int $microsecondOfDecade The value of the microsecond starting from the beginning of the current decade + * @property int $microsecondOfHour The value of the microsecond starting from the beginning of the current hour + * @property int $microsecondOfMillennium The value of the microsecond starting from the beginning of the current millennium + * @property int $microsecondOfMillisecond The value of the microsecond starting from the beginning of the current millisecond + * @property int $microsecondOfMinute The value of the microsecond starting from the beginning of the current minute + * @property int $microsecondOfMonth The value of the microsecond starting from the beginning of the current month + * @property int $microsecondOfQuarter The value of the microsecond starting from the beginning of the current quarter + * @property int $microsecondOfSecond The value of the microsecond starting from the beginning of the current second + * @property int $microsecondOfWeek The value of the microsecond starting from the beginning of the current week + * @property int $microsecondOfYear The value of the microsecond starting from the beginning of the current year + * @property int $millisecondOfCentury The value of the millisecond starting from the beginning of the current century + * @property int $millisecondOfDay The value of the millisecond starting from the beginning of the current day + * @property int $millisecondOfDecade The value of the millisecond starting from the beginning of the current decade + * @property int $millisecondOfHour The value of the millisecond starting from the beginning of the current hour + * @property int $millisecondOfMillennium The value of the millisecond starting from the beginning of the current millennium + * @property int $millisecondOfMinute The value of the millisecond starting from the beginning of the current minute + * @property int $millisecondOfMonth The value of the millisecond starting from the beginning of the current month + * @property int $millisecondOfQuarter The value of the millisecond starting from the beginning of the current quarter + * @property int $millisecondOfSecond The value of the millisecond starting from the beginning of the current second + * @property int $millisecondOfWeek The value of the millisecond starting from the beginning of the current week + * @property int $millisecondOfYear The value of the millisecond starting from the beginning of the current year + * @property int $minuteOfCentury The value of the minute starting from the beginning of the current century + * @property int $minuteOfDay The value of the minute starting from the beginning of the current day + * @property int $minuteOfDecade The value of the minute starting from the beginning of the current decade + * @property int $minuteOfHour The value of the minute starting from the beginning of the current hour + * @property int $minuteOfMillennium The value of the minute starting from the beginning of the current millennium + * @property int $minuteOfMonth The value of the minute starting from the beginning of the current month + * @property int $minuteOfQuarter The value of the minute starting from the beginning of the current quarter + * @property int $minuteOfWeek The value of the minute starting from the beginning of the current week + * @property int $minuteOfYear The value of the minute starting from the beginning of the current year + * @property int $monthOfCentury The value of the month starting from the beginning of the current century + * @property int $monthOfDecade The value of the month starting from the beginning of the current decade + * @property int $monthOfMillennium The value of the month starting from the beginning of the current millennium + * @property int $monthOfQuarter The value of the month starting from the beginning of the current quarter + * @property int $monthOfYear The value of the month starting from the beginning of the current year + * @property int $quarterOfCentury The value of the quarter starting from the beginning of the current century + * @property int $quarterOfDecade The value of the quarter starting from the beginning of the current decade + * @property int $quarterOfMillennium The value of the quarter starting from the beginning of the current millennium + * @property int $quarterOfYear The value of the quarter starting from the beginning of the current year + * @property int $secondOfCentury The value of the second starting from the beginning of the current century + * @property int $secondOfDay The value of the second starting from the beginning of the current day + * @property int $secondOfDecade The value of the second starting from the beginning of the current decade + * @property int $secondOfHour The value of the second starting from the beginning of the current hour + * @property int $secondOfMillennium The value of the second starting from the beginning of the current millennium + * @property int $secondOfMinute The value of the second starting from the beginning of the current minute + * @property int $secondOfMonth The value of the second starting from the beginning of the current month + * @property int $secondOfQuarter The value of the second starting from the beginning of the current quarter + * @property int $secondOfWeek The value of the second starting from the beginning of the current week + * @property int $secondOfYear The value of the second starting from the beginning of the current year + * @property int $weekOfCentury The value of the week starting from the beginning of the current century + * @property int $weekOfDecade The value of the week starting from the beginning of the current decade + * @property int $weekOfMillennium The value of the week starting from the beginning of the current millennium + * @property int $weekOfMonth 1 through 5 + * @property int $weekOfQuarter The value of the week starting from the beginning of the current quarter + * @property int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property int $yearOfCentury The value of the year starting from the beginning of the current century + * @property int $yearOfDecade The value of the year starting from the beginning of the current decade + * @property int $yearOfMillennium The value of the year starting from the beginning of the current millennium + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * @property-read int $centuriesInMillennium The number of centuries contained in the current millennium + * @property-read int $daysInCentury The number of days contained in the current century + * @property-read int $daysInDecade The number of days contained in the current decade + * @property-read int $daysInMillennium The number of days contained in the current millennium + * @property-read int $daysInMonth number of days in the given month + * @property-read int $daysInQuarter The number of days contained in the current quarter + * @property-read int $daysInWeek The number of days contained in the current week + * @property-read int $daysInYear 365 or 366 + * @property-read int $decadesInCentury The number of decades contained in the current century + * @property-read int $decadesInMillennium The number of decades contained in the current millennium + * @property-read int $hoursInCentury The number of hours contained in the current century + * @property-read int $hoursInDay The number of hours contained in the current day + * @property-read int $hoursInDecade The number of hours contained in the current decade + * @property-read int $hoursInMillennium The number of hours contained in the current millennium + * @property-read int $hoursInMonth The number of hours contained in the current month + * @property-read int $hoursInQuarter The number of hours contained in the current quarter + * @property-read int $hoursInWeek The number of hours contained in the current week + * @property-read int $hoursInYear The number of hours contained in the current year + * @property-read int $microsecondsInCentury The number of microseconds contained in the current century + * @property-read int $microsecondsInDay The number of microseconds contained in the current day + * @property-read int $microsecondsInDecade The number of microseconds contained in the current decade + * @property-read int $microsecondsInHour The number of microseconds contained in the current hour + * @property-read int $microsecondsInMillennium The number of microseconds contained in the current millennium + * @property-read int $microsecondsInMillisecond The number of microseconds contained in the current millisecond + * @property-read int $microsecondsInMinute The number of microseconds contained in the current minute + * @property-read int $microsecondsInMonth The number of microseconds contained in the current month + * @property-read int $microsecondsInQuarter The number of microseconds contained in the current quarter + * @property-read int $microsecondsInSecond The number of microseconds contained in the current second + * @property-read int $microsecondsInWeek The number of microseconds contained in the current week + * @property-read int $microsecondsInYear The number of microseconds contained in the current year + * @property-read int $millisecondsInCentury The number of milliseconds contained in the current century + * @property-read int $millisecondsInDay The number of milliseconds contained in the current day + * @property-read int $millisecondsInDecade The number of milliseconds contained in the current decade + * @property-read int $millisecondsInHour The number of milliseconds contained in the current hour + * @property-read int $millisecondsInMillennium The number of milliseconds contained in the current millennium + * @property-read int $millisecondsInMinute The number of milliseconds contained in the current minute + * @property-read int $millisecondsInMonth The number of milliseconds contained in the current month + * @property-read int $millisecondsInQuarter The number of milliseconds contained in the current quarter + * @property-read int $millisecondsInSecond The number of milliseconds contained in the current second + * @property-read int $millisecondsInWeek The number of milliseconds contained in the current week + * @property-read int $millisecondsInYear The number of milliseconds contained in the current year + * @property-read int $minutesInCentury The number of minutes contained in the current century + * @property-read int $minutesInDay The number of minutes contained in the current day + * @property-read int $minutesInDecade The number of minutes contained in the current decade + * @property-read int $minutesInHour The number of minutes contained in the current hour + * @property-read int $minutesInMillennium The number of minutes contained in the current millennium + * @property-read int $minutesInMonth The number of minutes contained in the current month + * @property-read int $minutesInQuarter The number of minutes contained in the current quarter + * @property-read int $minutesInWeek The number of minutes contained in the current week + * @property-read int $minutesInYear The number of minutes contained in the current year + * @property-read int $monthsInCentury The number of months contained in the current century + * @property-read int $monthsInDecade The number of months contained in the current decade + * @property-read int $monthsInMillennium The number of months contained in the current millennium + * @property-read int $monthsInQuarter The number of months contained in the current quarter + * @property-read int $monthsInYear The number of months contained in the current year + * @property-read int $quartersInCentury The number of quarters contained in the current century + * @property-read int $quartersInDecade The number of quarters contained in the current decade + * @property-read int $quartersInMillennium The number of quarters contained in the current millennium + * @property-read int $quartersInYear The number of quarters contained in the current year + * @property-read int $secondsInCentury The number of seconds contained in the current century + * @property-read int $secondsInDay The number of seconds contained in the current day + * @property-read int $secondsInDecade The number of seconds contained in the current decade + * @property-read int $secondsInHour The number of seconds contained in the current hour + * @property-read int $secondsInMillennium The number of seconds contained in the current millennium + * @property-read int $secondsInMinute The number of seconds contained in the current minute + * @property-read int $secondsInMonth The number of seconds contained in the current month + * @property-read int $secondsInQuarter The number of seconds contained in the current quarter + * @property-read int $secondsInWeek The number of seconds contained in the current week + * @property-read int $secondsInYear The number of seconds contained in the current year + * @property-read int $weeksInCentury The number of weeks contained in the current century + * @property-read int $weeksInDecade The number of weeks contained in the current decade + * @property-read int $weeksInMillennium The number of weeks contained in the current millennium + * @property-read int $weeksInMonth The number of weeks contained in the current month + * @property-read int $weeksInQuarter The number of weeks contained in the current quarter + * @property-read int $weeksInYear 51 through 53 + * @property-read int $yearsInCentury The number of years contained in the current century + * @property-read int $yearsInDecade The number of years contained in the current decade + * @property-read int $yearsInMillennium The number of years contained in the current millennium * - * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) - * @method bool isLocal() Check if the current instance has non-UTC timezone. - * @method bool isValid() Check if the current instance is a valid date. - * @method bool isDST() Check if the current instance is in a daylight saving time. - * @method bool isSunday() Checks if the instance day is sunday. - * @method bool isMonday() Checks if the instance day is monday. - * @method bool isTuesday() Checks if the instance day is tuesday. - * @method bool isWednesday() Checks if the instance day is wednesday. - * @method bool isThursday() Checks if the instance day is thursday. - * @method bool isFriday() Checks if the instance day is friday. - * @method bool isSaturday() Checks if the instance day is saturday. - * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. - * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. - * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. - * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. - * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. - * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. - * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. - * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. - * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. - * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. - * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. - * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. - * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. - * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. - * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. - * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. - * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. - * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. - * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. - * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. - * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. - * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. - * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. - * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. - * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. - * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. - * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. - * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. - * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. - * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. - * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. - * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. - * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. - * @method CarbonImmutable years(int $value) Set current instance year to the given value. - * @method CarbonImmutable year(int $value) Set current instance year to the given value. - * @method CarbonImmutable setYears(int $value) Set current instance year to the given value. - * @method CarbonImmutable setYear(int $value) Set current instance year to the given value. - * @method CarbonImmutable months(int $value) Set current instance month to the given value. - * @method CarbonImmutable month(int $value) Set current instance month to the given value. - * @method CarbonImmutable setMonths(int $value) Set current instance month to the given value. - * @method CarbonImmutable setMonth(int $value) Set current instance month to the given value. - * @method CarbonImmutable days(int $value) Set current instance day to the given value. - * @method CarbonImmutable day(int $value) Set current instance day to the given value. - * @method CarbonImmutable setDays(int $value) Set current instance day to the given value. - * @method CarbonImmutable setDay(int $value) Set current instance day to the given value. - * @method CarbonImmutable hours(int $value) Set current instance hour to the given value. - * @method CarbonImmutable hour(int $value) Set current instance hour to the given value. - * @method CarbonImmutable setHours(int $value) Set current instance hour to the given value. - * @method CarbonImmutable setHour(int $value) Set current instance hour to the given value. - * @method CarbonImmutable minutes(int $value) Set current instance minute to the given value. - * @method CarbonImmutable minute(int $value) Set current instance minute to the given value. - * @method CarbonImmutable setMinutes(int $value) Set current instance minute to the given value. - * @method CarbonImmutable setMinute(int $value) Set current instance minute to the given value. - * @method CarbonImmutable seconds(int $value) Set current instance second to the given value. - * @method CarbonImmutable second(int $value) Set current instance second to the given value. - * @method CarbonImmutable setSeconds(int $value) Set current instance second to the given value. - * @method CarbonImmutable setSecond(int $value) Set current instance second to the given value. - * @method CarbonImmutable millis(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable milli(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable setMillis(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable setMilli(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable milliseconds(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable millisecond(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable setMilliseconds(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable setMillisecond(int $value) Set current instance millisecond to the given value. - * @method CarbonImmutable micros(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable micro(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable setMicros(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable setMicro(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable microseconds(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable microsecond(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable setMicroseconds(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable setMicrosecond(int $value) Set current instance microsecond to the given value. - * @method CarbonImmutable addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addYear() Add one year to the instance (using date interval). - * @method CarbonImmutable subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subYear() Sub one year to the instance (using date interval). - * @method CarbonImmutable addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMonth() Add one month to the instance (using date interval). - * @method CarbonImmutable subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMonth() Sub one month to the instance (using date interval). - * @method CarbonImmutable addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addDay() Add one day to the instance (using date interval). - * @method CarbonImmutable subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subDay() Sub one day to the instance (using date interval). - * @method CarbonImmutable addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addHour() Add one hour to the instance (using date interval). - * @method CarbonImmutable subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subHour() Sub one hour to the instance (using date interval). - * @method CarbonImmutable addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMinute() Add one minute to the instance (using date interval). - * @method CarbonImmutable subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMinute() Sub one minute to the instance (using date interval). - * @method CarbonImmutable addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addSecond() Add one second to the instance (using date interval). - * @method CarbonImmutable subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subSecond() Sub one second to the instance (using date interval). - * @method CarbonImmutable addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMilli() Add one millisecond to the instance (using date interval). - * @method CarbonImmutable subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMilli() Sub one millisecond to the instance (using date interval). - * @method CarbonImmutable addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMillisecond() Add one millisecond to the instance (using date interval). - * @method CarbonImmutable subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMillisecond() Sub one millisecond to the instance (using date interval). - * @method CarbonImmutable addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMicro() Add one microsecond to the instance (using date interval). - * @method CarbonImmutable subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMicro() Sub one microsecond to the instance (using date interval). - * @method CarbonImmutable addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMicrosecond() Add one microsecond to the instance (using date interval). - * @method CarbonImmutable subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMicrosecond() Sub one microsecond to the instance (using date interval). - * @method CarbonImmutable addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addMillennium() Add one millennium to the instance (using date interval). - * @method CarbonImmutable subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subMillennium() Sub one millennium to the instance (using date interval). - * @method CarbonImmutable addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addCentury() Add one century to the instance (using date interval). - * @method CarbonImmutable subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subCentury() Sub one century to the instance (using date interval). - * @method CarbonImmutable addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addDecade() Add one decade to the instance (using date interval). - * @method CarbonImmutable subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subDecade() Sub one decade to the instance (using date interval). - * @method CarbonImmutable addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addQuarter() Add one quarter to the instance (using date interval). - * @method CarbonImmutable subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subQuarter() Sub one quarter to the instance (using date interval). - * @method CarbonImmutable addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonImmutable addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonImmutable addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addWeek() Add one week to the instance (using date interval). - * @method CarbonImmutable subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subWeek() Sub one week to the instance (using date interval). - * @method CarbonImmutable addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable addWeekday() Add one weekday to the instance (using date interval). - * @method CarbonImmutable subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). - * @method CarbonImmutable subWeekday() Sub one weekday to the instance (using date interval). - * @method CarbonImmutable addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMicro() Add one microsecond to the instance (using timestamp). - * @method CarbonImmutable subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMicro() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method CarbonImmutable addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMicrosecond() Add one microsecond to the instance (using timestamp). - * @method CarbonImmutable subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMicrosecond() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method CarbonImmutable addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMilli() Add one millisecond to the instance (using timestamp). - * @method CarbonImmutable subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMilli() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method CarbonImmutable addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMillisecond() Add one millisecond to the instance (using timestamp). - * @method CarbonImmutable subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMillisecond() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method CarbonImmutable addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealSecond() Add one second to the instance (using timestamp). - * @method CarbonImmutable subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealSecond() Sub one second to the instance (using timestamp). - * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. - * @method CarbonImmutable addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMinute() Add one minute to the instance (using timestamp). - * @method CarbonImmutable subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMinute() Sub one minute to the instance (using timestamp). - * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. - * @method CarbonImmutable addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealHour() Add one hour to the instance (using timestamp). - * @method CarbonImmutable subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealHour() Sub one hour to the instance (using timestamp). - * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. - * @method CarbonImmutable addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealDay() Add one day to the instance (using timestamp). - * @method CarbonImmutable subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealDay() Sub one day to the instance (using timestamp). - * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. - * @method CarbonImmutable addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealWeek() Add one week to the instance (using timestamp). - * @method CarbonImmutable subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealWeek() Sub one week to the instance (using timestamp). - * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. - * @method CarbonImmutable addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMonth() Add one month to the instance (using timestamp). - * @method CarbonImmutable subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMonth() Sub one month to the instance (using timestamp). - * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. - * @method CarbonImmutable addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealQuarter() Add one quarter to the instance (using timestamp). - * @method CarbonImmutable subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealQuarter() Sub one quarter to the instance (using timestamp). - * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. - * @method CarbonImmutable addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealYear() Add one year to the instance (using timestamp). - * @method CarbonImmutable subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealYear() Sub one year to the instance (using timestamp). - * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. - * @method CarbonImmutable addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealDecade() Add one decade to the instance (using timestamp). - * @method CarbonImmutable subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealDecade() Sub one decade to the instance (using timestamp). - * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. - * @method CarbonImmutable addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealCentury() Add one century to the instance (using timestamp). - * @method CarbonImmutable subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealCentury() Sub one century to the instance (using timestamp). - * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. - * @method CarbonImmutable addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable addRealMillennium() Add one millennium to the instance (using timestamp). - * @method CarbonImmutable subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). - * @method CarbonImmutable subRealMillennium() Sub one millennium to the instance (using timestamp). - * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. - * @method CarbonImmutable roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method CarbonImmutable roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method CarbonImmutable floorYear(float $precision = 1) Truncate the current instance year with given precision. - * @method CarbonImmutable floorYears(float $precision = 1) Truncate the current instance year with given precision. - * @method CarbonImmutable ceilYear(float $precision = 1) Ceil the current instance year with given precision. - * @method CarbonImmutable ceilYears(float $precision = 1) Ceil the current instance year with given precision. - * @method CarbonImmutable roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method CarbonImmutable roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method CarbonImmutable floorMonth(float $precision = 1) Truncate the current instance month with given precision. - * @method CarbonImmutable floorMonths(float $precision = 1) Truncate the current instance month with given precision. - * @method CarbonImmutable ceilMonth(float $precision = 1) Ceil the current instance month with given precision. - * @method CarbonImmutable ceilMonths(float $precision = 1) Ceil the current instance month with given precision. - * @method CarbonImmutable roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method CarbonImmutable roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method CarbonImmutable floorDay(float $precision = 1) Truncate the current instance day with given precision. - * @method CarbonImmutable floorDays(float $precision = 1) Truncate the current instance day with given precision. - * @method CarbonImmutable ceilDay(float $precision = 1) Ceil the current instance day with given precision. - * @method CarbonImmutable ceilDays(float $precision = 1) Ceil the current instance day with given precision. - * @method CarbonImmutable roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method CarbonImmutable roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method CarbonImmutable floorHour(float $precision = 1) Truncate the current instance hour with given precision. - * @method CarbonImmutable floorHours(float $precision = 1) Truncate the current instance hour with given precision. - * @method CarbonImmutable ceilHour(float $precision = 1) Ceil the current instance hour with given precision. - * @method CarbonImmutable ceilHours(float $precision = 1) Ceil the current instance hour with given precision. - * @method CarbonImmutable roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method CarbonImmutable roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method CarbonImmutable floorMinute(float $precision = 1) Truncate the current instance minute with given precision. - * @method CarbonImmutable floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. - * @method CarbonImmutable ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. - * @method CarbonImmutable ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. - * @method CarbonImmutable roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method CarbonImmutable roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method CarbonImmutable floorSecond(float $precision = 1) Truncate the current instance second with given precision. - * @method CarbonImmutable floorSeconds(float $precision = 1) Truncate the current instance second with given precision. - * @method CarbonImmutable ceilSecond(float $precision = 1) Ceil the current instance second with given precision. - * @method CarbonImmutable ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. - * @method CarbonImmutable roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method CarbonImmutable roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method CarbonImmutable floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. - * @method CarbonImmutable floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. - * @method CarbonImmutable ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. - * @method CarbonImmutable ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. - * @method CarbonImmutable roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method CarbonImmutable roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method CarbonImmutable floorCentury(float $precision = 1) Truncate the current instance century with given precision. - * @method CarbonImmutable floorCenturies(float $precision = 1) Truncate the current instance century with given precision. - * @method CarbonImmutable ceilCentury(float $precision = 1) Ceil the current instance century with given precision. - * @method CarbonImmutable ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. - * @method CarbonImmutable roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method CarbonImmutable roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method CarbonImmutable floorDecade(float $precision = 1) Truncate the current instance decade with given precision. - * @method CarbonImmutable floorDecades(float $precision = 1) Truncate the current instance decade with given precision. - * @method CarbonImmutable ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. - * @method CarbonImmutable ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. - * @method CarbonImmutable roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method CarbonImmutable roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method CarbonImmutable floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. - * @method CarbonImmutable floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. - * @method CarbonImmutable ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. - * @method CarbonImmutable ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. - * @method CarbonImmutable roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method CarbonImmutable roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method CarbonImmutable floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method CarbonImmutable floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method CarbonImmutable ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method CarbonImmutable ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method CarbonImmutable roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method CarbonImmutable roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method CarbonImmutable floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method CarbonImmutable floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method CarbonImmutable ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method CarbonImmutable ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method static static|false createFromFormat(string $format, string $time, DateTimeZone|string|false|null $timezone = null) Parse a string into a new CarbonImmutable object according to the specified format. - * @method static static __set_state(array $array) https://php.net/manual/en/datetime.set-state.php + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(DateTimeInterface|string $date) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isSameWeek(DateTimeInterface|string $date) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(DateTimeInterface|string $date) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(DateTimeInterface|string $date) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(DateTimeInterface|string $date) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(DateTimeInterface|string $date) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMilli(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMilli() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMilli() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMilli() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMillisecond(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillisecond() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMillisecond() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMillisecond() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMicro(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameDecade(DateTimeInterface|string $date) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(DateTimeInterface|string $date) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(DateTimeInterface|string $date) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method CarbonImmutable years(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonImmutable year(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonImmutable setYears(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonImmutable setYear(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonImmutable months(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonImmutable month(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonImmutable setMonths(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonImmutable setMonth(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonImmutable days(int $value) Set current instance day to the given value. + * @method CarbonImmutable day(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDays(int $value) Set current instance day to the given value. + * @method CarbonImmutable setDay(int $value) Set current instance day to the given value. + * @method CarbonImmutable hours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable hour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHours(int $value) Set current instance hour to the given value. + * @method CarbonImmutable setHour(int $value) Set current instance hour to the given value. + * @method CarbonImmutable minutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable minute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonImmutable setMinute(int $value) Set current instance minute to the given value. + * @method CarbonImmutable seconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable second(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSeconds(int $value) Set current instance second to the given value. + * @method CarbonImmutable setSecond(int $value) Set current instance second to the given value. + * @method CarbonImmutable millis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonImmutable micros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable micro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method self setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonImmutable addYears(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addYear() Add one year to the instance (using date interval). + * @method CarbonImmutable subYears(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subYear() Sub one year to the instance (using date interval). + * @method CarbonImmutable addYearsWithOverflow(int|float $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearsWithOverflow(int|float $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonths(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMonth() Add one month to the instance (using date interval). + * @method CarbonImmutable subMonths(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMonth() Sub one month to the instance (using date interval). + * @method CarbonImmutable addMonthsWithOverflow(int|float $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthsWithOverflow(int|float $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDay() Add one day to the instance (using date interval). + * @method CarbonImmutable subDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDay() Sub one day to the instance (using date interval). + * @method CarbonImmutable addHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addHour() Add one hour to the instance (using date interval). + * @method CarbonImmutable subHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subHour() Sub one hour to the instance (using date interval). + * @method CarbonImmutable addMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMinute() Add one minute to the instance (using date interval). + * @method CarbonImmutable subMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMinute() Sub one minute to the instance (using date interval). + * @method CarbonImmutable addSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addSecond() Add one second to the instance (using date interval). + * @method CarbonImmutable subSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subSecond() Sub one second to the instance (using date interval). + * @method CarbonImmutable addMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonImmutable subMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonImmutable addMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonImmutable subMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonImmutable addMillennia(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonImmutable subMillennia(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonImmutable addMillenniaWithOverflow(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniaWithOverflow(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturies(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addCentury() Add one century to the instance (using date interval). + * @method CarbonImmutable subCenturies(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subCentury() Sub one century to the instance (using date interval). + * @method CarbonImmutable addCenturiesWithOverflow(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturiesWithOverflow(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecades(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addDecade() Add one decade to the instance (using date interval). + * @method CarbonImmutable subDecades(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subDecade() Sub one decade to the instance (using date interval). + * @method CarbonImmutable addDecadesWithOverflow(int|float $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadesWithOverflow(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarters(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonImmutable subQuarters(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonImmutable addQuartersWithOverflow(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuartersWithOverflow(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonImmutable addQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonImmutable addWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeek() Add one week to the instance (using date interval). + * @method CarbonImmutable subWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeek() Sub one week to the instance (using date interval). + * @method CarbonImmutable addWeekdays(int|float $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonImmutable subWeekdays(int|float $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonImmutable subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonImmutable addUTCMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subUTCMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicros(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method CarbonImmutable addUTCMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonImmutable subUTCMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicroseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method CarbonImmutable addUTCMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subUTCMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMillis(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method CarbonImmutable addUTCMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonImmutable subUTCMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMilliseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method CarbonImmutable addUTCSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCSecond() Add one second to the instance (using timestamp). + * @method CarbonImmutable subUTCSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method float diffInUTCSeconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of seconds. + * @method CarbonImmutable addUTCMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMinute() Add one minute to the instance (using timestamp). + * @method CarbonImmutable subUTCMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method float diffInUTCMinutes(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of minutes. + * @method CarbonImmutable addUTCHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCHour() Add one hour to the instance (using timestamp). + * @method CarbonImmutable subUTCHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method float diffInUTCHours(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of hours. + * @method CarbonImmutable addUTCDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCDay() Add one day to the instance (using timestamp). + * @method CarbonImmutable subUTCDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method float diffInUTCDays(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of days. + * @method CarbonImmutable addUTCWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCWeek() Add one week to the instance (using timestamp). + * @method CarbonImmutable subUTCWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method float diffInUTCWeeks(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of weeks. + * @method CarbonImmutable addUTCMonths(int|float $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMonth() Add one month to the instance (using timestamp). + * @method CarbonImmutable subUTCMonths(int|float $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method float diffInUTCMonths(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of months. + * @method CarbonImmutable addUTCQuarters(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonImmutable subUTCQuarters(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method float diffInUTCQuarters(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of quarters. + * @method CarbonImmutable addUTCYears(int|float $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCYear() Add one year to the instance (using timestamp). + * @method CarbonImmutable subUTCYears(int|float $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method float diffInUTCYears(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of years. + * @method CarbonImmutable addUTCDecades(int|float $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCDecade() Add one decade to the instance (using timestamp). + * @method CarbonImmutable subUTCDecades(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method float diffInUTCDecades(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of decades. + * @method CarbonImmutable addUTCCenturies(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCCentury() Add one century to the instance (using timestamp). + * @method CarbonImmutable subUTCCenturies(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method float diffInUTCCenturies(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of centuries. + * @method CarbonImmutable addUTCMillennia(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable addUTCMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonImmutable subUTCMillennia(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonImmutable subUTCMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method float diffInUTCMillennia(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of millennia. + * @method CarbonImmutable roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonImmutable floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonImmutable ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonImmutable roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonImmutable floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonImmutable ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonImmutable roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonImmutable floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonImmutable ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonImmutable roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonImmutable floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonImmutable ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonImmutable roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonImmutable floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonImmutable ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonImmutable roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonImmutable floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonImmutable ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonImmutable roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonImmutable floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonImmutable roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonImmutable floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonImmutable ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonImmutable roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonImmutable floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonImmutable ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonImmutable roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonImmutable floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonImmutable roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonImmutable floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonImmutable ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonImmutable roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonImmutable floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonImmutable ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method int centuriesInMillennium() Return the number of centuries contained in the current millennium + * @method int|static centuryOfMillennium(?int $century = null) Return the value of the century starting from the beginning of the current millennium when called with no parameters, change the current century when called with an integer value + * @method int|static dayOfCentury(?int $day = null) Return the value of the day starting from the beginning of the current century when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfDecade(?int $day = null) Return the value of the day starting from the beginning of the current decade when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMillennium(?int $day = null) Return the value of the day starting from the beginning of the current millennium when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMonth(?int $day = null) Return the value of the day starting from the beginning of the current month when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfQuarter(?int $day = null) Return the value of the day starting from the beginning of the current quarter when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfWeek(?int $day = null) Return the value of the day starting from the beginning of the current week when called with no parameters, change the current day when called with an integer value + * @method int daysInCentury() Return the number of days contained in the current century + * @method int daysInDecade() Return the number of days contained in the current decade + * @method int daysInMillennium() Return the number of days contained in the current millennium + * @method int daysInMonth() Return the number of days contained in the current month + * @method int daysInQuarter() Return the number of days contained in the current quarter + * @method int daysInWeek() Return the number of days contained in the current week + * @method int daysInYear() Return the number of days contained in the current year + * @method int|static decadeOfCentury(?int $decade = null) Return the value of the decade starting from the beginning of the current century when called with no parameters, change the current decade when called with an integer value + * @method int|static decadeOfMillennium(?int $decade = null) Return the value of the decade starting from the beginning of the current millennium when called with no parameters, change the current decade when called with an integer value + * @method int decadesInCentury() Return the number of decades contained in the current century + * @method int decadesInMillennium() Return the number of decades contained in the current millennium + * @method int|static hourOfCentury(?int $hour = null) Return the value of the hour starting from the beginning of the current century when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDay(?int $hour = null) Return the value of the hour starting from the beginning of the current day when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDecade(?int $hour = null) Return the value of the hour starting from the beginning of the current decade when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMillennium(?int $hour = null) Return the value of the hour starting from the beginning of the current millennium when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMonth(?int $hour = null) Return the value of the hour starting from the beginning of the current month when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfQuarter(?int $hour = null) Return the value of the hour starting from the beginning of the current quarter when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfWeek(?int $hour = null) Return the value of the hour starting from the beginning of the current week when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfYear(?int $hour = null) Return the value of the hour starting from the beginning of the current year when called with no parameters, change the current hour when called with an integer value + * @method int hoursInCentury() Return the number of hours contained in the current century + * @method int hoursInDay() Return the number of hours contained in the current day + * @method int hoursInDecade() Return the number of hours contained in the current decade + * @method int hoursInMillennium() Return the number of hours contained in the current millennium + * @method int hoursInMonth() Return the number of hours contained in the current month + * @method int hoursInQuarter() Return the number of hours contained in the current quarter + * @method int hoursInWeek() Return the number of hours contained in the current week + * @method int hoursInYear() Return the number of hours contained in the current year + * @method int|static microsecondOfCentury(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current century when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDay(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current day when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDecade(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current decade when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfHour(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current hour when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillennium(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millennium when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillisecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millisecond when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMinute(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current minute when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMonth(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current month when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfQuarter(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current quarter when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfSecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current second when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfWeek(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current week when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfYear(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current year when called with no parameters, change the current microsecond when called with an integer value + * @method int microsecondsInCentury() Return the number of microseconds contained in the current century + * @method int microsecondsInDay() Return the number of microseconds contained in the current day + * @method int microsecondsInDecade() Return the number of microseconds contained in the current decade + * @method int microsecondsInHour() Return the number of microseconds contained in the current hour + * @method int microsecondsInMillennium() Return the number of microseconds contained in the current millennium + * @method int microsecondsInMillisecond() Return the number of microseconds contained in the current millisecond + * @method int microsecondsInMinute() Return the number of microseconds contained in the current minute + * @method int microsecondsInMonth() Return the number of microseconds contained in the current month + * @method int microsecondsInQuarter() Return the number of microseconds contained in the current quarter + * @method int microsecondsInSecond() Return the number of microseconds contained in the current second + * @method int microsecondsInWeek() Return the number of microseconds contained in the current week + * @method int microsecondsInYear() Return the number of microseconds contained in the current year + * @method int|static millisecondOfCentury(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current century when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDay(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current day when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDecade(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current decade when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfHour(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current hour when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMillennium(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current millennium when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMinute(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current minute when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMonth(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current month when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfQuarter(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current quarter when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfSecond(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current second when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfWeek(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current week when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfYear(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current year when called with no parameters, change the current millisecond when called with an integer value + * @method int millisecondsInCentury() Return the number of milliseconds contained in the current century + * @method int millisecondsInDay() Return the number of milliseconds contained in the current day + * @method int millisecondsInDecade() Return the number of milliseconds contained in the current decade + * @method int millisecondsInHour() Return the number of milliseconds contained in the current hour + * @method int millisecondsInMillennium() Return the number of milliseconds contained in the current millennium + * @method int millisecondsInMinute() Return the number of milliseconds contained in the current minute + * @method int millisecondsInMonth() Return the number of milliseconds contained in the current month + * @method int millisecondsInQuarter() Return the number of milliseconds contained in the current quarter + * @method int millisecondsInSecond() Return the number of milliseconds contained in the current second + * @method int millisecondsInWeek() Return the number of milliseconds contained in the current week + * @method int millisecondsInYear() Return the number of milliseconds contained in the current year + * @method int|static minuteOfCentury(?int $minute = null) Return the value of the minute starting from the beginning of the current century when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDay(?int $minute = null) Return the value of the minute starting from the beginning of the current day when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDecade(?int $minute = null) Return the value of the minute starting from the beginning of the current decade when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfHour(?int $minute = null) Return the value of the minute starting from the beginning of the current hour when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMillennium(?int $minute = null) Return the value of the minute starting from the beginning of the current millennium when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMonth(?int $minute = null) Return the value of the minute starting from the beginning of the current month when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfQuarter(?int $minute = null) Return the value of the minute starting from the beginning of the current quarter when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfWeek(?int $minute = null) Return the value of the minute starting from the beginning of the current week when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfYear(?int $minute = null) Return the value of the minute starting from the beginning of the current year when called with no parameters, change the current minute when called with an integer value + * @method int minutesInCentury() Return the number of minutes contained in the current century + * @method int minutesInDay() Return the number of minutes contained in the current day + * @method int minutesInDecade() Return the number of minutes contained in the current decade + * @method int minutesInHour() Return the number of minutes contained in the current hour + * @method int minutesInMillennium() Return the number of minutes contained in the current millennium + * @method int minutesInMonth() Return the number of minutes contained in the current month + * @method int minutesInQuarter() Return the number of minutes contained in the current quarter + * @method int minutesInWeek() Return the number of minutes contained in the current week + * @method int minutesInYear() Return the number of minutes contained in the current year + * @method int|static monthOfCentury(?int $month = null) Return the value of the month starting from the beginning of the current century when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfDecade(?int $month = null) Return the value of the month starting from the beginning of the current decade when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfMillennium(?int $month = null) Return the value of the month starting from the beginning of the current millennium when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfQuarter(?int $month = null) Return the value of the month starting from the beginning of the current quarter when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfYear(?int $month = null) Return the value of the month starting from the beginning of the current year when called with no parameters, change the current month when called with an integer value + * @method int monthsInCentury() Return the number of months contained in the current century + * @method int monthsInDecade() Return the number of months contained in the current decade + * @method int monthsInMillennium() Return the number of months contained in the current millennium + * @method int monthsInQuarter() Return the number of months contained in the current quarter + * @method int monthsInYear() Return the number of months contained in the current year + * @method int|static quarterOfCentury(?int $quarter = null) Return the value of the quarter starting from the beginning of the current century when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfDecade(?int $quarter = null) Return the value of the quarter starting from the beginning of the current decade when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfMillennium(?int $quarter = null) Return the value of the quarter starting from the beginning of the current millennium when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfYear(?int $quarter = null) Return the value of the quarter starting from the beginning of the current year when called with no parameters, change the current quarter when called with an integer value + * @method int quartersInCentury() Return the number of quarters contained in the current century + * @method int quartersInDecade() Return the number of quarters contained in the current decade + * @method int quartersInMillennium() Return the number of quarters contained in the current millennium + * @method int quartersInYear() Return the number of quarters contained in the current year + * @method int|static secondOfCentury(?int $second = null) Return the value of the second starting from the beginning of the current century when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDay(?int $second = null) Return the value of the second starting from the beginning of the current day when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDecade(?int $second = null) Return the value of the second starting from the beginning of the current decade when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfHour(?int $second = null) Return the value of the second starting from the beginning of the current hour when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMillennium(?int $second = null) Return the value of the second starting from the beginning of the current millennium when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMinute(?int $second = null) Return the value of the second starting from the beginning of the current minute when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMonth(?int $second = null) Return the value of the second starting from the beginning of the current month when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfQuarter(?int $second = null) Return the value of the second starting from the beginning of the current quarter when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfWeek(?int $second = null) Return the value of the second starting from the beginning of the current week when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfYear(?int $second = null) Return the value of the second starting from the beginning of the current year when called with no parameters, change the current second when called with an integer value + * @method int secondsInCentury() Return the number of seconds contained in the current century + * @method int secondsInDay() Return the number of seconds contained in the current day + * @method int secondsInDecade() Return the number of seconds contained in the current decade + * @method int secondsInHour() Return the number of seconds contained in the current hour + * @method int secondsInMillennium() Return the number of seconds contained in the current millennium + * @method int secondsInMinute() Return the number of seconds contained in the current minute + * @method int secondsInMonth() Return the number of seconds contained in the current month + * @method int secondsInQuarter() Return the number of seconds contained in the current quarter + * @method int secondsInWeek() Return the number of seconds contained in the current week + * @method int secondsInYear() Return the number of seconds contained in the current year + * @method int|static weekOfCentury(?int $week = null) Return the value of the week starting from the beginning of the current century when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfDecade(?int $week = null) Return the value of the week starting from the beginning of the current decade when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMillennium(?int $week = null) Return the value of the week starting from the beginning of the current millennium when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMonth(?int $week = null) Return the value of the week starting from the beginning of the current month when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfQuarter(?int $week = null) Return the value of the week starting from the beginning of the current quarter when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfYear(?int $week = null) Return the value of the week starting from the beginning of the current year when called with no parameters, change the current week when called with an integer value + * @method int weeksInCentury() Return the number of weeks contained in the current century + * @method int weeksInDecade() Return the number of weeks contained in the current decade + * @method int weeksInMillennium() Return the number of weeks contained in the current millennium + * @method int weeksInMonth() Return the number of weeks contained in the current month + * @method int weeksInQuarter() Return the number of weeks contained in the current quarter + * @method int|static yearOfCentury(?int $year = null) Return the value of the year starting from the beginning of the current century when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfDecade(?int $year = null) Return the value of the year starting from the beginning of the current decade when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfMillennium(?int $year = null) Return the value of the year starting from the beginning of the current millennium when called with no parameters, change the current year when called with an integer value + * @method int yearsInCentury() Return the number of years contained in the current century + * @method int yearsInDecade() Return the number of years contained in the current decade + * @method int yearsInMillennium() Return the number of years contained in the current millennium + * @method static plus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Add given amount of time to the current date. + * @method static minus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Subtract given amount of time to the current date. * * */ @@ -511,21 +845,154 @@ class CarbonImmutable extends DateTimeImmutable implements CarbonInterface { use Date { __clone as dateTraitClone; + __call as dateTraitCall; + modify as dateTraitModify; + add as dateTraitAdd; + sub as dateTraitSub; + setDate as dateTraitSetDate; + setISODate as dateTraitSetISODate; + setTime as dateTraitSetTime; + setTimestamp as dateTraitSetTimestamp; + setTimezone as dateTraitSetTimezone; + next as dateTraitNext; + previous as dateTraitPrevious; } - public function __clone() + public function __clone(): void { $this->dateTraitClone(); $this->endOfTime = false; $this->startOfTime = false; } + /* + * The methods below only forward to the Date trait. They are re-declared so that they + * carry the #[NoDiscard] attribute (PHP >= 8.5) as their DateTimeImmutable + * counterparts do: as the instance is never modified, ignoring the result is + * most likely a mistake. + */ + + #[NoDiscard] + public function __call(string $method, array $parameters): mixed + { + return $this->dateTraitCall($method, $parameters); + } + + /** + * @see Date::modify() + * + * @return static + */ + #[NoDiscard] + #[ReturnTypeWillChange] + public function modify($modify) + { + return $this->dateTraitModify($modify); + } + + /** + * @see Date::add() + * + * @param Unit|int|string|DateInterval|Duration|Closure|CarbonConverterInterface $unit + * @param Unit|int|float|string $value + */ + #[NoDiscard] + #[ReturnTypeWillChange] + public function add($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static + { + return $this->dateTraitAdd(...\func_get_args()); + } + + /** + * @see Date::sub() + * + * @param Unit|int|string|DateInterval|Duration|Closure|CarbonConverterInterface $unit + * @param Unit|int|float|string $value + */ + #[NoDiscard] + #[ReturnTypeWillChange] + public function sub($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static + { + return $this->dateTraitSub(...\func_get_args()); + } + + /** + * @see Date::setDate() + */ + #[NoDiscard] + public function setDate(int $year, int $month, int $day): static + { + return $this->dateTraitSetDate($year, $month, $day); + } + + /** + * @see Date::setISODate() + */ + #[NoDiscard] + public function setISODate(int $year, int $week, int $day = 1): static + { + return $this->dateTraitSetISODate($year, $week, $day); + } + + /** + * @see Date::setTime() + */ + #[NoDiscard] + public function setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0): static + { + return $this->dateTraitSetTime($hour, $minute, $second, $microseconds); + } + + /** + * @see Date::setTimestamp() + */ + #[NoDiscard] + public function setTimestamp(float|int|string $timestamp): static + { + return $this->dateTraitSetTimestamp($timestamp); + } + + /** + * @see Date::setTimezone() + */ + #[NoDiscard] + public function setTimezone(DateTimeZone|string|int $timeZone): static + { + return $this->dateTraitSetTimezone($timeZone); + } + + /** + * @see Date::next() + * + * @param string|int|null $modifier + * + * @return static + */ + #[NoDiscard] + public function next($modifier = null) + { + return $this->dateTraitNext($modifier); + } + + /** + * @see Date::previous() + * + * @param string|int|null $modifier + * + * @return static + */ + #[NoDiscard] + public function previous($modifier = null) + { + return $this->dateTraitPrevious($modifier); + } + /** * Create a very old date representing start of time. * * @return static */ - public static function startOfTime(): self + public static function startOfTime(): static { $date = static::parse('0001-01-01')->years(self::getStartOfTimeYear()); $date->startOfTime = true; @@ -538,7 +1005,7 @@ class CarbonImmutable extends DateTimeImmutable implements CarbonInterface * * @return static */ - public static function endOfTime(): self + public static function endOfTime(): static { $date = static::parse('9999-12-31 23:59:59.999999')->years(self::getEndOfTimeYear()); $date->endOfTime = true; @@ -551,16 +1018,7 @@ class CarbonImmutable extends DateTimeImmutable implements CarbonInterface */ private static function getEndOfTimeYear(): int { - if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { - return 145261681241552; - } - - // Remove if https://bugs.php.net/bug.php?id=81107 is fixed - if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { - return 1118290769066902787; - } - - return PHP_INT_MAX; + return 1118290769066902787; // PHP_INT_MAX no longer work since PHP 8.1 } /** @@ -568,15 +1026,6 @@ class CarbonImmutable extends DateTimeImmutable implements CarbonInterface */ private static function getStartOfTimeYear(): int { - if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { - return -135908816449551; - } - - // Remove if https://bugs.php.net/bug.php?id=81107 is fixed - if (version_compare(PHP_VERSION, '8.1.0-dev', '>=')) { - return -1118290769066898816; - } - - return max(PHP_INT_MIN, -9223372036854773760); + return -1118290769066898816; // PHP_INT_MIN no longer work since PHP 8.1 } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php index b90e29817..f17fe568b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterface.php @@ -1,5 +1,7 @@ * + * @property string $localeDayOfWeek the day of week in current locale + * @property string $shortLocaleDayOfWeek the abbreviated day of week in current locale + * @property string $localeMonth the month in current locale + * @property string $shortLocaleMonth the abbreviated month in current locale * @property int $year * @property int $yearIso * @property int $month @@ -45,592 +56,810 @@ use Throwable; * @property int $second * @property int $micro * @property int $microsecond - * @property int|float|string $timestamp seconds since the Unix Epoch - * @property string $englishDayOfWeek the day of week in English - * @property string $shortEnglishDayOfWeek the abbreviated day of week in English - * @property string $englishMonth the month in English - * @property string $shortEnglishMonth the abbreviated month in English + * @property int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English * @property int $milliseconds * @property int $millisecond * @property int $milli - * @property int $week 1 through 53 - * @property int $isoWeek 1 through 53 - * @property int $weekYear year according to week format - * @property int $isoWeekYear year according to ISO week format - * @property int $dayOfYear 1 through 366 - * @property int $age does a diffInYears() with default parameters - * @property int $offset the timezone offset in seconds from UTC - * @property int $offsetMinutes the timezone offset in minutes from UTC - * @property int $offsetHours the timezone offset in hours from UTC - * @property CarbonTimeZone $timezone the current timezone - * @property CarbonTimeZone $tz alias of $timezone - * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) - * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) - * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday - * @property-read int $daysInMonth number of days in the given month - * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) - * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) - * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name - * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName - * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read int $noZeroHour current hour from 1 to 24 - * @property-read int $weeksInYear 51 through 53 - * @property-read int $isoWeeksInYear 51 through 53 - * @property-read int $weekOfMonth 1 through 5 - * @property-read int $weekNumberInMonth 1 through 5 - * @property-read int $firstWeekDay 0 through 6 - * @property-read int $lastWeekDay 0 through 6 - * @property-read int $daysInYear 365 or 366 - * @property-read int $quarter the quarter of this instance, 1 - 4 - * @property-read int $decade the decade of this instance - * @property-read int $century the century of this instance - * @property-read int $millennium the millennium of this instance - * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise - * @property-read bool $local checks if the timezone is local, true if local, false otherwise - * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise - * @property-read string $timezoneName the current timezone name - * @property-read string $tzName alias of $timezoneName - * @property-read string $locale locale of the current instance + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property int $centuryOfMillennium The value of the century starting from the beginning of the current millennium + * @property int $dayOfCentury The value of the day starting from the beginning of the current century + * @property int $dayOfDecade The value of the day starting from the beginning of the current decade + * @property int $dayOfMillennium The value of the day starting from the beginning of the current millennium + * @property int $dayOfMonth The value of the day starting from the beginning of the current month + * @property int $dayOfQuarter The value of the day starting from the beginning of the current quarter + * @property int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property int $dayOfYear 1 through 366 + * @property int $decadeOfCentury The value of the decade starting from the beginning of the current century + * @property int $decadeOfMillennium The value of the decade starting from the beginning of the current millennium + * @property int $hourOfCentury The value of the hour starting from the beginning of the current century + * @property int $hourOfDay The value of the hour starting from the beginning of the current day + * @property int $hourOfDecade The value of the hour starting from the beginning of the current decade + * @property int $hourOfMillennium The value of the hour starting from the beginning of the current millennium + * @property int $hourOfMonth The value of the hour starting from the beginning of the current month + * @property int $hourOfQuarter The value of the hour starting from the beginning of the current quarter + * @property int $hourOfWeek The value of the hour starting from the beginning of the current week + * @property int $hourOfYear The value of the hour starting from the beginning of the current year + * @property int $microsecondOfCentury The value of the microsecond starting from the beginning of the current century + * @property int $microsecondOfDay The value of the microsecond starting from the beginning of the current day + * @property int $microsecondOfDecade The value of the microsecond starting from the beginning of the current decade + * @property int $microsecondOfHour The value of the microsecond starting from the beginning of the current hour + * @property int $microsecondOfMillennium The value of the microsecond starting from the beginning of the current millennium + * @property int $microsecondOfMillisecond The value of the microsecond starting from the beginning of the current millisecond + * @property int $microsecondOfMinute The value of the microsecond starting from the beginning of the current minute + * @property int $microsecondOfMonth The value of the microsecond starting from the beginning of the current month + * @property int $microsecondOfQuarter The value of the microsecond starting from the beginning of the current quarter + * @property int $microsecondOfSecond The value of the microsecond starting from the beginning of the current second + * @property int $microsecondOfWeek The value of the microsecond starting from the beginning of the current week + * @property int $microsecondOfYear The value of the microsecond starting from the beginning of the current year + * @property int $millisecondOfCentury The value of the millisecond starting from the beginning of the current century + * @property int $millisecondOfDay The value of the millisecond starting from the beginning of the current day + * @property int $millisecondOfDecade The value of the millisecond starting from the beginning of the current decade + * @property int $millisecondOfHour The value of the millisecond starting from the beginning of the current hour + * @property int $millisecondOfMillennium The value of the millisecond starting from the beginning of the current millennium + * @property int $millisecondOfMinute The value of the millisecond starting from the beginning of the current minute + * @property int $millisecondOfMonth The value of the millisecond starting from the beginning of the current month + * @property int $millisecondOfQuarter The value of the millisecond starting from the beginning of the current quarter + * @property int $millisecondOfSecond The value of the millisecond starting from the beginning of the current second + * @property int $millisecondOfWeek The value of the millisecond starting from the beginning of the current week + * @property int $millisecondOfYear The value of the millisecond starting from the beginning of the current year + * @property int $minuteOfCentury The value of the minute starting from the beginning of the current century + * @property int $minuteOfDay The value of the minute starting from the beginning of the current day + * @property int $minuteOfDecade The value of the minute starting from the beginning of the current decade + * @property int $minuteOfHour The value of the minute starting from the beginning of the current hour + * @property int $minuteOfMillennium The value of the minute starting from the beginning of the current millennium + * @property int $minuteOfMonth The value of the minute starting from the beginning of the current month + * @property int $minuteOfQuarter The value of the minute starting from the beginning of the current quarter + * @property int $minuteOfWeek The value of the minute starting from the beginning of the current week + * @property int $minuteOfYear The value of the minute starting from the beginning of the current year + * @property int $monthOfCentury The value of the month starting from the beginning of the current century + * @property int $monthOfDecade The value of the month starting from the beginning of the current decade + * @property int $monthOfMillennium The value of the month starting from the beginning of the current millennium + * @property int $monthOfQuarter The value of the month starting from the beginning of the current quarter + * @property int $monthOfYear The value of the month starting from the beginning of the current year + * @property int $quarterOfCentury The value of the quarter starting from the beginning of the current century + * @property int $quarterOfDecade The value of the quarter starting from the beginning of the current decade + * @property int $quarterOfMillennium The value of the quarter starting from the beginning of the current millennium + * @property int $quarterOfYear The value of the quarter starting from the beginning of the current year + * @property int $secondOfCentury The value of the second starting from the beginning of the current century + * @property int $secondOfDay The value of the second starting from the beginning of the current day + * @property int $secondOfDecade The value of the second starting from the beginning of the current decade + * @property int $secondOfHour The value of the second starting from the beginning of the current hour + * @property int $secondOfMillennium The value of the second starting from the beginning of the current millennium + * @property int $secondOfMinute The value of the second starting from the beginning of the current minute + * @property int $secondOfMonth The value of the second starting from the beginning of the current month + * @property int $secondOfQuarter The value of the second starting from the beginning of the current quarter + * @property int $secondOfWeek The value of the second starting from the beginning of the current week + * @property int $secondOfYear The value of the second starting from the beginning of the current year + * @property int $weekOfCentury The value of the week starting from the beginning of the current century + * @property int $weekOfDecade The value of the week starting from the beginning of the current decade + * @property int $weekOfMillennium The value of the week starting from the beginning of the current millennium + * @property int $weekOfMonth 1 through 5 + * @property int $weekOfQuarter The value of the week starting from the beginning of the current quarter + * @property int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property int $yearOfCentury The value of the year starting from the beginning of the current century + * @property int $yearOfDecade The value of the year starting from the beginning of the current decade + * @property int $yearOfMillennium The value of the year starting from the beginning of the current millennium + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * @property-read int $centuriesInMillennium The number of centuries contained in the current millennium + * @property-read int $daysInCentury The number of days contained in the current century + * @property-read int $daysInDecade The number of days contained in the current decade + * @property-read int $daysInMillennium The number of days contained in the current millennium + * @property-read int $daysInMonth number of days in the given month + * @property-read int $daysInQuarter The number of days contained in the current quarter + * @property-read int $daysInWeek The number of days contained in the current week + * @property-read int $daysInYear 365 or 366 + * @property-read int $decadesInCentury The number of decades contained in the current century + * @property-read int $decadesInMillennium The number of decades contained in the current millennium + * @property-read int $hoursInCentury The number of hours contained in the current century + * @property-read int $hoursInDay The number of hours contained in the current day + * @property-read int $hoursInDecade The number of hours contained in the current decade + * @property-read int $hoursInMillennium The number of hours contained in the current millennium + * @property-read int $hoursInMonth The number of hours contained in the current month + * @property-read int $hoursInQuarter The number of hours contained in the current quarter + * @property-read int $hoursInWeek The number of hours contained in the current week + * @property-read int $hoursInYear The number of hours contained in the current year + * @property-read int $microsecondsInCentury The number of microseconds contained in the current century + * @property-read int $microsecondsInDay The number of microseconds contained in the current day + * @property-read int $microsecondsInDecade The number of microseconds contained in the current decade + * @property-read int $microsecondsInHour The number of microseconds contained in the current hour + * @property-read int $microsecondsInMillennium The number of microseconds contained in the current millennium + * @property-read int $microsecondsInMillisecond The number of microseconds contained in the current millisecond + * @property-read int $microsecondsInMinute The number of microseconds contained in the current minute + * @property-read int $microsecondsInMonth The number of microseconds contained in the current month + * @property-read int $microsecondsInQuarter The number of microseconds contained in the current quarter + * @property-read int $microsecondsInSecond The number of microseconds contained in the current second + * @property-read int $microsecondsInWeek The number of microseconds contained in the current week + * @property-read int $microsecondsInYear The number of microseconds contained in the current year + * @property-read int $millisecondsInCentury The number of milliseconds contained in the current century + * @property-read int $millisecondsInDay The number of milliseconds contained in the current day + * @property-read int $millisecondsInDecade The number of milliseconds contained in the current decade + * @property-read int $millisecondsInHour The number of milliseconds contained in the current hour + * @property-read int $millisecondsInMillennium The number of milliseconds contained in the current millennium + * @property-read int $millisecondsInMinute The number of milliseconds contained in the current minute + * @property-read int $millisecondsInMonth The number of milliseconds contained in the current month + * @property-read int $millisecondsInQuarter The number of milliseconds contained in the current quarter + * @property-read int $millisecondsInSecond The number of milliseconds contained in the current second + * @property-read int $millisecondsInWeek The number of milliseconds contained in the current week + * @property-read int $millisecondsInYear The number of milliseconds contained in the current year + * @property-read int $minutesInCentury The number of minutes contained in the current century + * @property-read int $minutesInDay The number of minutes contained in the current day + * @property-read int $minutesInDecade The number of minutes contained in the current decade + * @property-read int $minutesInHour The number of minutes contained in the current hour + * @property-read int $minutesInMillennium The number of minutes contained in the current millennium + * @property-read int $minutesInMonth The number of minutes contained in the current month + * @property-read int $minutesInQuarter The number of minutes contained in the current quarter + * @property-read int $minutesInWeek The number of minutes contained in the current week + * @property-read int $minutesInYear The number of minutes contained in the current year + * @property-read int $monthsInCentury The number of months contained in the current century + * @property-read int $monthsInDecade The number of months contained in the current decade + * @property-read int $monthsInMillennium The number of months contained in the current millennium + * @property-read int $monthsInQuarter The number of months contained in the current quarter + * @property-read int $monthsInYear The number of months contained in the current year + * @property-read int $quartersInCentury The number of quarters contained in the current century + * @property-read int $quartersInDecade The number of quarters contained in the current decade + * @property-read int $quartersInMillennium The number of quarters contained in the current millennium + * @property-read int $quartersInYear The number of quarters contained in the current year + * @property-read int $secondsInCentury The number of seconds contained in the current century + * @property-read int $secondsInDay The number of seconds contained in the current day + * @property-read int $secondsInDecade The number of seconds contained in the current decade + * @property-read int $secondsInHour The number of seconds contained in the current hour + * @property-read int $secondsInMillennium The number of seconds contained in the current millennium + * @property-read int $secondsInMinute The number of seconds contained in the current minute + * @property-read int $secondsInMonth The number of seconds contained in the current month + * @property-read int $secondsInQuarter The number of seconds contained in the current quarter + * @property-read int $secondsInWeek The number of seconds contained in the current week + * @property-read int $secondsInYear The number of seconds contained in the current year + * @property-read int $weeksInCentury The number of weeks contained in the current century + * @property-read int $weeksInDecade The number of weeks contained in the current decade + * @property-read int $weeksInMillennium The number of weeks contained in the current millennium + * @property-read int $weeksInMonth The number of weeks contained in the current month + * @property-read int $weeksInQuarter The number of weeks contained in the current quarter + * @property-read int $weeksInYear 51 through 53 + * @property-read int $yearsInCentury The number of years contained in the current century + * @property-read int $yearsInDecade The number of years contained in the current decade + * @property-read int $yearsInMillennium The number of years contained in the current millennium * - * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) - * @method bool isLocal() Check if the current instance has non-UTC timezone. - * @method bool isValid() Check if the current instance is a valid date. - * @method bool isDST() Check if the current instance is in a daylight saving time. - * @method bool isSunday() Checks if the instance day is sunday. - * @method bool isMonday() Checks if the instance day is monday. - * @method bool isTuesday() Checks if the instance day is tuesday. - * @method bool isWednesday() Checks if the instance day is wednesday. - * @method bool isThursday() Checks if the instance day is thursday. - * @method bool isFriday() Checks if the instance day is friday. - * @method bool isSaturday() Checks if the instance day is saturday. - * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. - * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. - * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. - * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. - * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. - * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. - * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. - * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. - * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. - * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. - * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. - * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. - * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. - * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. - * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. - * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. - * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. - * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. - * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. - * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. - * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. - * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. - * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. - * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. - * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. - * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. - * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. - * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. - * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. - * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. - * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. - * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. - * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. - * @method CarbonInterface years(int $value) Set current instance year to the given value. - * @method CarbonInterface year(int $value) Set current instance year to the given value. - * @method CarbonInterface setYears(int $value) Set current instance year to the given value. - * @method CarbonInterface setYear(int $value) Set current instance year to the given value. - * @method CarbonInterface months(int $value) Set current instance month to the given value. - * @method CarbonInterface month(int $value) Set current instance month to the given value. - * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. - * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. - * @method CarbonInterface days(int $value) Set current instance day to the given value. - * @method CarbonInterface day(int $value) Set current instance day to the given value. - * @method CarbonInterface setDays(int $value) Set current instance day to the given value. - * @method CarbonInterface setDay(int $value) Set current instance day to the given value. - * @method CarbonInterface hours(int $value) Set current instance hour to the given value. - * @method CarbonInterface hour(int $value) Set current instance hour to the given value. - * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. - * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. - * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. - * @method CarbonInterface minute(int $value) Set current instance minute to the given value. - * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. - * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. - * @method CarbonInterface seconds(int $value) Set current instance second to the given value. - * @method CarbonInterface second(int $value) Set current instance second to the given value. - * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. - * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. - * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addYear() Add one year to the instance (using date interval). - * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subYear() Sub one year to the instance (using date interval). - * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMonth() Add one month to the instance (using date interval). - * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). - * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addDay() Add one day to the instance (using date interval). - * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subDay() Sub one day to the instance (using date interval). - * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addHour() Add one hour to the instance (using date interval). - * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). - * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). - * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). - * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addSecond() Add one second to the instance (using date interval). - * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). - * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). - * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). - * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). - * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). - * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). - * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). - * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). - * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). - * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). - * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). - * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addCentury() Add one century to the instance (using date interval). - * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). - * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). - * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). - * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). - * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). - * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addWeek() Add one week to the instance (using date interval). - * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). - * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). - * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). - * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). - * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). - * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). - * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). - * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). - * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). - * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. - * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). - * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). - * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. - * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). - * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). - * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. - * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). - * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). - * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. - * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). - * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). - * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. - * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). - * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). - * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. - * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). - * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). - * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. - * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). - * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). - * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. - * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). - * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). - * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. - * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). - * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). - * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. - * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). - * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). - * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. - * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. - * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. - * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. - * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. - * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. - * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. - * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. - * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. - * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. - * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. - * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. - * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. - * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. - * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. - * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. - * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. - * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. - * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. - * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. - * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. - * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. - * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. - * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. - * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. - * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. - * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. - * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. - * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. - * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. - * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. - * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. - * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. - * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. - * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. - * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. - * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. - * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. - * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. - * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. - * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. - * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(DateTimeInterface|string $date) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isSameWeek(DateTimeInterface|string $date) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(DateTimeInterface|string $date) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(DateTimeInterface|string $date) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(DateTimeInterface|string $date) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(DateTimeInterface|string $date) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMilli(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMilli() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMilli() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMilli() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMillisecond(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillisecond() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMillisecond() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMillisecond() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMicro(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameDecade(DateTimeInterface|string $date) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(DateTimeInterface|string $date) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(DateTimeInterface|string $date) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method CarbonInterface years(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface year(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface months(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface month(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface setMonths(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface setMonth(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method self setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int|float $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int|float $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int|float $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int|float $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int|float $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int|float $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int|float $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addUTCMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subUTCMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicros(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method CarbonInterface addUTCMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subUTCMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicroseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method CarbonInterface addUTCMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subUTCMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMillis(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method CarbonInterface addUTCMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subUTCMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMilliseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method CarbonInterface addUTCSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subUTCSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method float diffInUTCSeconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of seconds. + * @method CarbonInterface addUTCMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subUTCMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method float diffInUTCMinutes(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of minutes. + * @method CarbonInterface addUTCHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subUTCHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method float diffInUTCHours(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of hours. + * @method CarbonInterface addUTCDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subUTCDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method float diffInUTCDays(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of days. + * @method CarbonInterface addUTCWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subUTCWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method float diffInUTCWeeks(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of weeks. + * @method CarbonInterface addUTCMonths(int|float $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subUTCMonths(int|float $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method float diffInUTCMonths(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of months. + * @method CarbonInterface addUTCQuarters(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subUTCQuarters(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method float diffInUTCQuarters(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of quarters. + * @method CarbonInterface addUTCYears(int|float $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subUTCYears(int|float $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method float diffInUTCYears(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of years. + * @method CarbonInterface addUTCDecades(int|float $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subUTCDecades(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method float diffInUTCDecades(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of decades. + * @method CarbonInterface addUTCCenturies(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subUTCCenturies(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method float diffInUTCCenturies(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of centuries. + * @method CarbonInterface addUTCMillennia(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subUTCMillennia(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method float diffInUTCMillennia(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of millennia. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method int centuriesInMillennium() Return the number of centuries contained in the current millennium + * @method int|static centuryOfMillennium(?int $century = null) Return the value of the century starting from the beginning of the current millennium when called with no parameters, change the current century when called with an integer value + * @method int|static dayOfCentury(?int $day = null) Return the value of the day starting from the beginning of the current century when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfDecade(?int $day = null) Return the value of the day starting from the beginning of the current decade when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMillennium(?int $day = null) Return the value of the day starting from the beginning of the current millennium when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMonth(?int $day = null) Return the value of the day starting from the beginning of the current month when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfQuarter(?int $day = null) Return the value of the day starting from the beginning of the current quarter when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfWeek(?int $day = null) Return the value of the day starting from the beginning of the current week when called with no parameters, change the current day when called with an integer value + * @method int daysInCentury() Return the number of days contained in the current century + * @method int daysInDecade() Return the number of days contained in the current decade + * @method int daysInMillennium() Return the number of days contained in the current millennium + * @method int daysInMonth() Return the number of days contained in the current month + * @method int daysInQuarter() Return the number of days contained in the current quarter + * @method int daysInWeek() Return the number of days contained in the current week + * @method int daysInYear() Return the number of days contained in the current year + * @method int|static decadeOfCentury(?int $decade = null) Return the value of the decade starting from the beginning of the current century when called with no parameters, change the current decade when called with an integer value + * @method int|static decadeOfMillennium(?int $decade = null) Return the value of the decade starting from the beginning of the current millennium when called with no parameters, change the current decade when called with an integer value + * @method int decadesInCentury() Return the number of decades contained in the current century + * @method int decadesInMillennium() Return the number of decades contained in the current millennium + * @method int|static hourOfCentury(?int $hour = null) Return the value of the hour starting from the beginning of the current century when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDay(?int $hour = null) Return the value of the hour starting from the beginning of the current day when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDecade(?int $hour = null) Return the value of the hour starting from the beginning of the current decade when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMillennium(?int $hour = null) Return the value of the hour starting from the beginning of the current millennium when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMonth(?int $hour = null) Return the value of the hour starting from the beginning of the current month when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfQuarter(?int $hour = null) Return the value of the hour starting from the beginning of the current quarter when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfWeek(?int $hour = null) Return the value of the hour starting from the beginning of the current week when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfYear(?int $hour = null) Return the value of the hour starting from the beginning of the current year when called with no parameters, change the current hour when called with an integer value + * @method int hoursInCentury() Return the number of hours contained in the current century + * @method int hoursInDay() Return the number of hours contained in the current day + * @method int hoursInDecade() Return the number of hours contained in the current decade + * @method int hoursInMillennium() Return the number of hours contained in the current millennium + * @method int hoursInMonth() Return the number of hours contained in the current month + * @method int hoursInQuarter() Return the number of hours contained in the current quarter + * @method int hoursInWeek() Return the number of hours contained in the current week + * @method int hoursInYear() Return the number of hours contained in the current year + * @method int|static microsecondOfCentury(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current century when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDay(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current day when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDecade(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current decade when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfHour(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current hour when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillennium(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millennium when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillisecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millisecond when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMinute(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current minute when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMonth(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current month when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfQuarter(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current quarter when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfSecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current second when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfWeek(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current week when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfYear(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current year when called with no parameters, change the current microsecond when called with an integer value + * @method int microsecondsInCentury() Return the number of microseconds contained in the current century + * @method int microsecondsInDay() Return the number of microseconds contained in the current day + * @method int microsecondsInDecade() Return the number of microseconds contained in the current decade + * @method int microsecondsInHour() Return the number of microseconds contained in the current hour + * @method int microsecondsInMillennium() Return the number of microseconds contained in the current millennium + * @method int microsecondsInMillisecond() Return the number of microseconds contained in the current millisecond + * @method int microsecondsInMinute() Return the number of microseconds contained in the current minute + * @method int microsecondsInMonth() Return the number of microseconds contained in the current month + * @method int microsecondsInQuarter() Return the number of microseconds contained in the current quarter + * @method int microsecondsInSecond() Return the number of microseconds contained in the current second + * @method int microsecondsInWeek() Return the number of microseconds contained in the current week + * @method int microsecondsInYear() Return the number of microseconds contained in the current year + * @method int|static millisecondOfCentury(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current century when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDay(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current day when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDecade(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current decade when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfHour(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current hour when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMillennium(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current millennium when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMinute(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current minute when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMonth(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current month when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfQuarter(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current quarter when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfSecond(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current second when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfWeek(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current week when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfYear(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current year when called with no parameters, change the current millisecond when called with an integer value + * @method int millisecondsInCentury() Return the number of milliseconds contained in the current century + * @method int millisecondsInDay() Return the number of milliseconds contained in the current day + * @method int millisecondsInDecade() Return the number of milliseconds contained in the current decade + * @method int millisecondsInHour() Return the number of milliseconds contained in the current hour + * @method int millisecondsInMillennium() Return the number of milliseconds contained in the current millennium + * @method int millisecondsInMinute() Return the number of milliseconds contained in the current minute + * @method int millisecondsInMonth() Return the number of milliseconds contained in the current month + * @method int millisecondsInQuarter() Return the number of milliseconds contained in the current quarter + * @method int millisecondsInSecond() Return the number of milliseconds contained in the current second + * @method int millisecondsInWeek() Return the number of milliseconds contained in the current week + * @method int millisecondsInYear() Return the number of milliseconds contained in the current year + * @method int|static minuteOfCentury(?int $minute = null) Return the value of the minute starting from the beginning of the current century when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDay(?int $minute = null) Return the value of the minute starting from the beginning of the current day when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDecade(?int $minute = null) Return the value of the minute starting from the beginning of the current decade when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfHour(?int $minute = null) Return the value of the minute starting from the beginning of the current hour when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMillennium(?int $minute = null) Return the value of the minute starting from the beginning of the current millennium when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMonth(?int $minute = null) Return the value of the minute starting from the beginning of the current month when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfQuarter(?int $minute = null) Return the value of the minute starting from the beginning of the current quarter when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfWeek(?int $minute = null) Return the value of the minute starting from the beginning of the current week when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfYear(?int $minute = null) Return the value of the minute starting from the beginning of the current year when called with no parameters, change the current minute when called with an integer value + * @method int minutesInCentury() Return the number of minutes contained in the current century + * @method int minutesInDay() Return the number of minutes contained in the current day + * @method int minutesInDecade() Return the number of minutes contained in the current decade + * @method int minutesInHour() Return the number of minutes contained in the current hour + * @method int minutesInMillennium() Return the number of minutes contained in the current millennium + * @method int minutesInMonth() Return the number of minutes contained in the current month + * @method int minutesInQuarter() Return the number of minutes contained in the current quarter + * @method int minutesInWeek() Return the number of minutes contained in the current week + * @method int minutesInYear() Return the number of minutes contained in the current year + * @method int|static monthOfCentury(?int $month = null) Return the value of the month starting from the beginning of the current century when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfDecade(?int $month = null) Return the value of the month starting from the beginning of the current decade when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfMillennium(?int $month = null) Return the value of the month starting from the beginning of the current millennium when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfQuarter(?int $month = null) Return the value of the month starting from the beginning of the current quarter when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfYear(?int $month = null) Return the value of the month starting from the beginning of the current year when called with no parameters, change the current month when called with an integer value + * @method int monthsInCentury() Return the number of months contained in the current century + * @method int monthsInDecade() Return the number of months contained in the current decade + * @method int monthsInMillennium() Return the number of months contained in the current millennium + * @method int monthsInQuarter() Return the number of months contained in the current quarter + * @method int monthsInYear() Return the number of months contained in the current year + * @method int|static quarterOfCentury(?int $quarter = null) Return the value of the quarter starting from the beginning of the current century when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfDecade(?int $quarter = null) Return the value of the quarter starting from the beginning of the current decade when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfMillennium(?int $quarter = null) Return the value of the quarter starting from the beginning of the current millennium when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfYear(?int $quarter = null) Return the value of the quarter starting from the beginning of the current year when called with no parameters, change the current quarter when called with an integer value + * @method int quartersInCentury() Return the number of quarters contained in the current century + * @method int quartersInDecade() Return the number of quarters contained in the current decade + * @method int quartersInMillennium() Return the number of quarters contained in the current millennium + * @method int quartersInYear() Return the number of quarters contained in the current year + * @method int|static secondOfCentury(?int $second = null) Return the value of the second starting from the beginning of the current century when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDay(?int $second = null) Return the value of the second starting from the beginning of the current day when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDecade(?int $second = null) Return the value of the second starting from the beginning of the current decade when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfHour(?int $second = null) Return the value of the second starting from the beginning of the current hour when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMillennium(?int $second = null) Return the value of the second starting from the beginning of the current millennium when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMinute(?int $second = null) Return the value of the second starting from the beginning of the current minute when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMonth(?int $second = null) Return the value of the second starting from the beginning of the current month when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfQuarter(?int $second = null) Return the value of the second starting from the beginning of the current quarter when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfWeek(?int $second = null) Return the value of the second starting from the beginning of the current week when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfYear(?int $second = null) Return the value of the second starting from the beginning of the current year when called with no parameters, change the current second when called with an integer value + * @method int secondsInCentury() Return the number of seconds contained in the current century + * @method int secondsInDay() Return the number of seconds contained in the current day + * @method int secondsInDecade() Return the number of seconds contained in the current decade + * @method int secondsInHour() Return the number of seconds contained in the current hour + * @method int secondsInMillennium() Return the number of seconds contained in the current millennium + * @method int secondsInMinute() Return the number of seconds contained in the current minute + * @method int secondsInMonth() Return the number of seconds contained in the current month + * @method int secondsInQuarter() Return the number of seconds contained in the current quarter + * @method int secondsInWeek() Return the number of seconds contained in the current week + * @method int secondsInYear() Return the number of seconds contained in the current year + * @method int|static weekOfCentury(?int $week = null) Return the value of the week starting from the beginning of the current century when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfDecade(?int $week = null) Return the value of the week starting from the beginning of the current decade when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMillennium(?int $week = null) Return the value of the week starting from the beginning of the current millennium when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMonth(?int $week = null) Return the value of the week starting from the beginning of the current month when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfQuarter(?int $week = null) Return the value of the week starting from the beginning of the current quarter when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfYear(?int $week = null) Return the value of the week starting from the beginning of the current year when called with no parameters, change the current week when called with an integer value + * @method int weeksInCentury() Return the number of weeks contained in the current century + * @method int weeksInDecade() Return the number of weeks contained in the current decade + * @method int weeksInMillennium() Return the number of weeks contained in the current millennium + * @method int weeksInMonth() Return the number of weeks contained in the current month + * @method int weeksInQuarter() Return the number of weeks contained in the current quarter + * @method int|static yearOfCentury(?int $year = null) Return the value of the year starting from the beginning of the current century when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfDecade(?int $year = null) Return the value of the year starting from the beginning of the current decade when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfMillennium(?int $year = null) Return the value of the year starting from the beginning of the current millennium when called with no parameters, change the current year when called with an integer value + * @method int yearsInCentury() Return the number of years contained in the current century + * @method int yearsInDecade() Return the number of years contained in the current decade + * @method int yearsInMillennium() Return the number of years contained in the current millennium + * @method static plus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Add given amount of time to the current date. + * @method static minus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Subtract given amount of time to the current date. * * + * + * @codeCoverageIgnore */ -interface CarbonInterface extends DateTimeInterface, JsonSerializable +interface CarbonInterface extends DateTimeInterface, JsonSerializable, DiffOptions, Format, TranslationOptions, UnitValue { - /** - * Diff wording options(expressed in octal). - */ - public const NO_ZERO_DIFF = 01; - public const JUST_NOW = 02; - public const ONE_DAY_WORDS = 04; - public const TWO_DAY_WORDS = 010; - public const SEQUENTIAL_PARTS_ONLY = 020; - public const ROUND = 040; - public const FLOOR = 0100; - public const CEIL = 0200; - - /** - * Diff syntax options. - */ - public const DIFF_ABSOLUTE = 1; // backward compatibility with true - public const DIFF_RELATIVE_AUTO = 0; // backward compatibility with false - public const DIFF_RELATIVE_TO_NOW = 2; - public const DIFF_RELATIVE_TO_OTHER = 3; - - /** - * Translate string options. - */ - public const TRANSLATE_MONTHS = 1; - public const TRANSLATE_DAYS = 2; - public const TRANSLATE_UNITS = 4; - public const TRANSLATE_MERIDIEM = 8; - public const TRANSLATE_DIFF = 0x10; - public const TRANSLATE_ALL = self::TRANSLATE_MONTHS | self::TRANSLATE_DAYS | self::TRANSLATE_UNITS | self::TRANSLATE_MERIDIEM | self::TRANSLATE_DIFF; - - /** - * The day constants. - */ - public const SUNDAY = 0; - public const MONDAY = 1; - public const TUESDAY = 2; - public const WEDNESDAY = 3; - public const THURSDAY = 4; - public const FRIDAY = 5; - public const SATURDAY = 6; - - /** - * The month constants. - * These aren't used by Carbon itself but exist for - * convenience sake alone. - */ - public const JANUARY = 1; - public const FEBRUARY = 2; - public const MARCH = 3; - public const APRIL = 4; - public const MAY = 5; - public const JUNE = 6; - public const JULY = 7; - public const AUGUST = 8; - public const SEPTEMBER = 9; - public const OCTOBER = 10; - public const NOVEMBER = 11; - public const DECEMBER = 12; - - /** - * Number of X in Y. - */ - public const YEARS_PER_MILLENNIUM = 1000; - public const YEARS_PER_CENTURY = 100; - public const YEARS_PER_DECADE = 10; - public const MONTHS_PER_YEAR = 12; - public const MONTHS_PER_QUARTER = 3; - public const QUARTERS_PER_YEAR = 4; - public const WEEKS_PER_YEAR = 52; - public const WEEKS_PER_MONTH = 4; - public const DAYS_PER_YEAR = 365; - public const DAYS_PER_WEEK = 7; - public const HOURS_PER_DAY = 24; - public const MINUTES_PER_HOUR = 60; - public const SECONDS_PER_MINUTE = 60; - public const MILLISECONDS_PER_SECOND = 1000; - public const MICROSECONDS_PER_MILLISECOND = 1000; - public const MICROSECONDS_PER_SECOND = 1000000; - - /** - * Special settings to get the start of week from current locale culture. - */ - public const WEEK_DAY_AUTO = 'auto'; - - /** - * RFC7231 DateTime format. - * - * @var string - */ - public const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; - - /** - * Default format to use for __toString method when type juggling occurs. - * - * @var string - */ - public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; - - /** - * Format for converting mocked time, includes microseconds. - * - * @var string - */ - public const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; - - /** - * Pattern detection for ->isoFormat and ::createFromIsoFormat. - * - * @var string - */ - public const ISO_FORMAT_REGEXP = '(O[YMDHhms]|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY?|g{1,5}|G{1,5}|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?)'; - // /** @@ -640,10 +869,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param array $parameters parameters list * * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable - * - * @return mixed */ - public function __call($method, $parameters); + public function __call(string $method, array $parameters): mixed; /** * Dynamically handle calls to the class. @@ -652,15 +879,13 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param array $parameters parameters list * * @throws BadMethodCallException - * - * @return mixed */ - public static function __callStatic($method, $parameters); + public static function __callStatic(string $method, array $parameters): mixed; /** * Update constructedObjectId on cloned. */ - public function __clone(); + public function __clone(): void; /** * Create a new Carbon instance. @@ -668,30 +893,23 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Please see the testing aids section (specifically static::setTestNow()) * for more on the possibility of this constructor returning a test instance. * - * @param DateTimeInterface|string|null $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException */ - public function __construct($time = null, $tz = null); + public function __construct(DateTimeInterface|WeekDay|Month|string|int|float|null $time = null, DateTimeZone|string|int|null $timezone = null); /** * Show truthy properties on var_dump(). - * - * @return array */ - public function __debugInfo(); + public function __debugInfo(): array; /** - * Get a part of the Carbon object - * - * @param string $name + * Get a part of the Carbon object. * * @throws UnknownGetterException * * @return string|int|bool|DateTimeZone|null */ - public function __get($name); + public function __get(string $name): mixed; /** * Check if an attribute exists on the object @@ -722,16 +940,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @return static */ #[ReturnTypeWillChange] - public static function __set_state($dump); - - /** - * Returns the list of properties to dump on serialize() called on. - * - * Only used by PHP < 7.4. - * - * @return array - */ - public function __sleep(); + public static function __set_state($dump): static; /** * Format the instance as a string using the set format @@ -740,8 +949,6 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now(); // Carbon instances can be cast to string * ``` - * - * @return string */ public function __toString(); @@ -752,47 +959,54 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @example $date->add(15, 'days') * @example $date->add(CarbonInterval::days(4)) * - * @param string|DateInterval|Closure|CarbonConverterInterface $unit - * @param int $value - * @param bool|null $overflow + * @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface|Duration $unit + * @param Unit|int|float|string $value + * @param OverflowMode|bool|null $overflow + * @param int|null $anchorDay * * @return static */ #[ReturnTypeWillChange] - public function add($unit, $value = 1, $overflow = null); + public function add($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static; + + /** + * @deprecated Prefer to use add addUTCUnit() which more accurately defines what it's doing. + * + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int|float|null $value + * + * @return static + */ + public function addRealUnit(string $unit, $value = 1): static; /** * Add seconds to the instance using timestamp. Positive $value travels * forward while negative $value travels into the past. * - * @param string $unit - * @param int $value + * @param string $unit + * @param int|float|null $value * * @return static */ - public function addRealUnit($unit, $value = 1); + public function addUTCUnit(string $unit, $value = 1): static; /** * Add given units to the current instance. - * - * @param string $unit - * @param int $value - * @param bool|null $overflow - * - * @return static */ - public function addUnit($unit, $value = 1, $overflow = null); + public function addUnit(Unit|string $unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static; /** * Add any unit to a new value without overflowing current other unit given. * - * @param string $valueUnit unit name to modify - * @param int $value amount to add to the input unit - * @param string $overflowUnit unit name to not overflow - * - * @return static + * @param Unit|string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param Unit|string $overflowUnit unit name to not overflow + * @param ?int $anchorDay set day to this value after modification */ - public function addUnitNoOverflow($valueUnit, $value, $overflowUnit); + public function addUnitNoOverflow(Unit|string $valueUnit, int $value, Unit|string $overflowUnit, ?int $anchorDay = null): static; /** * Get the difference in a human readable format in the current locale from an other @@ -842,7 +1056,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return static */ - public function avoidMutation(); + public function avoidMutation(): static; /** * Determines if the instance is between two others. @@ -859,13 +1073,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * @param bool $equal Indicates if an equal to comparison should be done - * - * @return bool + * @param bool $equal Indicates if an equal to comparison should be done */ - public function between($date1, $date2, $equal = true): bool; + public function between(DateTimeInterface|string $date1, DateTimeInterface|string $date2, bool $equal = true): bool; /** * Determines if the instance is between two others, bounds excluded. @@ -876,13 +1086,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * - * @return bool */ - public function betweenExcluded($date1, $date2): bool; + public function betweenExcluded(DateTimeInterface|string $date1, DateTimeInterface|string $date2): bool; /** * Determines if the instance is between two others, bounds included. @@ -893,13 +1098,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * - * @return bool */ - public function betweenIncluded($date1, $date2): bool; + public function betweenIncluded(DateTimeInterface|string $date1, DateTimeInterface|string $date2): bool; /** * Returns either day of week + time (e.g. "Last Friday at 3:30 PM") if reference time is within 7 days, @@ -923,13 +1123,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false * ``` - * - * @param string $date - * @param string $format - * - * @return bool */ - public static function canBeCreatedFromFormat($date, $format); + public static function canBeCreatedFromFormat(?string $date, string $format): bool; /** * Return the Carbon instance passed through, a now instance in the same timezone @@ -944,39 +1139,30 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Cast the current instance into the given class. * - * @param string $className The $className::instance() method will be called to cast the current object. + * @template T * - * @return DateTimeInterface + * @param class-string $className The $className::instance() method will be called to cast the current object. + * + * @return T */ - public function cast(string $className); + public function cast(string $className): mixed; /** * Ceil the current instance second with given precision if specified. - * - * @param float|int|string|\DateInterval|null $precision - * - * @return CarbonInterface */ - public function ceil($precision = 1); + public function ceil(DateInterval|string|int|float $precision = 1): static; /** * Ceil the current instance at the given unit with given precision if specified. - * - * @param string $unit - * @param float|int $precision - * - * @return CarbonInterface */ - public function ceilUnit($unit, $precision = 1); + public function ceilUnit(string $unit, DateInterval|\Time\Duration|string|int|float $precision = 1): static; /** * Ceil the current instance week. * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week - * - * @return CarbonInterface + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week */ - public function ceilWeek($weekStartsAt = null); + public function ceilWeek(WeekDay|int|null $weekStartsAt = null): static; /** * Similar to native modify() method of DateTime but can handle more grammars. @@ -990,7 +1176,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param string $modifier * - * @return static|false + * @return static */ public function change($modifier); @@ -1041,140 +1227,130 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * If $hour is not null then the default values for $minute and $second * will be 0. * - * @param DateTimeInterface|int|null $year - * @param int|null $month - * @param int|null $day - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param DateTimeInterface|string|int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null); + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null): ?static; /** * Create a Carbon instance from just a date. The time portion is set to now. * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createFromDate($year = null, $month = null, $day = null, $tz = null); + public static function createFromDate($year = null, $month = null, $day = null, $timezone = null); /** * Create a Carbon instance from a specific format. * - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ #[ReturnTypeWillChange] - public static function createFromFormat($format, $time, $tz = null); + public static function createFromFormat($format, $time, $timezone = null): ?static; /** * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). * - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $tz optional timezone - * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) - * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * Era tokens (N to NNNNN, y, yy, yyy, yyyy, yo) are read with the "eras" of $locale: the era name gives the + * era and the year is the year of this era, so 'D MMMM N y' with 'th' reads '29 กุมภาพันธ์ พ.ศ. 2567' as + * 2024-02-29. Without any N token, the era of today's date in this locale is used. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|int|null $timezone optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param TranslatorInterface|null $translator optional custom translator to use for macro-formats * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null); + public static function createFromIsoFormat(string $format, string $time, $timezone = null, ?string $locale = 'en', ?TranslatorInterface $translator = null): ?static; /** * Create a Carbon instance from a specific format and a string in a given language. * - * @param string $format Datetime format - * @param string $locale - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function createFromLocaleFormat($format, $locale, $time, $tz = null); + public static function createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null): ?static; /** * Create a Carbon instance from a specific ISO format and a string in a given language. * - * @param string $format Datetime ISO format - * @param string $locale - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null); + public static function createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null): ?static; /** * Create a Carbon instance from just a time. The date portion is set to today. * - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null); + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $timezone = null): static; /** * Create a Carbon instance from a time string. The date portion is set to today. * - * @param string $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException - * - * @return static */ - public static function createFromTimeString($time, $tz = null); + public static function createFromTimeString(string $time, DateTimeZone|string|int|null $timezone = null): static; /** - * Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). + * Create a Carbon instance from a timestamp and set the timezone (UTC by default). * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $timestamp - * @param \DateTimeZone|string|null $tz - * - * @return static */ - public static function createFromTimestamp($timestamp, $tz = null); + #[ReturnTypeWillChange] + public static function createFromTimestamp(string|int|float $timestamp, DateTimeZone|string|int|null $timezone = null): static; /** * Create a Carbon instance from a timestamp in milliseconds. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $timestamp - * @param \DateTimeZone|string|null $tz - * - * @return static */ - public static function createFromTimestampMs($timestamp, $tz = null); + public static function createFromTimestampMs(string|int|float $timestamp, DateTimeZone|string|int|null $timezone = null): static; /** * Create a Carbon instance from a timestamp in milliseconds. @@ -1185,32 +1361,28 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return static */ - public static function createFromTimestampMsUTC($timestamp); + public static function createFromTimestampMsUTC($timestamp): static; /** - * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Create a Carbon instance from a timestamp keeping the timezone to UTC. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $timestamp - * - * @return static */ - public static function createFromTimestampUTC($timestamp); + public static function createFromTimestampUTC(string|int|float $timestamp): static; /** * Create a Carbon instance from just a date. The time portion is set to midnight. * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null); + public static function createMidnightDate($year = null, $month = null, $day = null, $timezone = null); /** * Create a new safe Carbon instance from a specific date and time. @@ -1227,47 +1399,47 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * If one of the set values is not valid, an InvalidDateException * will be thrown. * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidDateException * - * @return static|false + * @return static|null */ - public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null); + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null): ?static; /** * Create a new Carbon instance from a specific date and time using strict validation. * * @see create() * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null); + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $timezone = null): static; /** * Get/set the day of year. * * @param int|null $value new value for day of year if using as setter. * - * @return static|int + * @return ($value is null ? int : static) */ - public function dayOfYear($value = null); + public function dayOfYear(?int $value = null): static|int; /** * Get the difference as a CarbonInterval instance. @@ -1279,7 +1451,31 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return CarbonInterval */ - public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []); + public function diffAsCarbonInterval($date = null, bool $absolute = false, array $skip = []): CarbonInterval; + + /** + * Get the difference as a DateInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return DateInterval + */ + public function diffAsDateInterval($date = null, bool $absolute = false): DateInterval; + + /** + * Get the difference as a \Time\Duration instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return Duration + */ + public function diffAsDuration($date = null, bool $absolute = false): \Time\Duration; /** * Get the difference by the given interval using a filter closure. @@ -1291,7 +1487,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return int */ - public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true); + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, bool $absolute = false): int; /** * Get the difference in a human readable format in the current locale from current instance to an other @@ -1306,54 +1502,58 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; * ``` * - * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; - * if null passed, now will be used as comparison reference; - * if any other type, it will be converted to date and used as reference. - * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: - * - 'syntax' entry (see below) - * - 'short' entry (see below) - * - 'parts' entry (see below) - * - 'options' entry (see below) - * - 'skip' entry, list of units to skip (array of strings or a single string, - * ` it can be the unit name (singular or plural) or its shortcut - * ` (y, m, w, d, h, min, s, ms, µs). - * - 'aUnit' entry, prefer "an hour" over "1 hour" if true - * - 'join' entry determines how to join multiple parts of the string - * ` - if $join is a string, it's used as a joiner glue - * ` - if $join is a callable/closure, it get the list of string and should return a string - * ` - if $join is an array, the first item will be the default glue, and the second item - * ` will be used instead of the glue for the last item - * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) - * ` - if $join is missing, a space will be used as glue - * - 'other' entry (see above) - * - 'minimumUnit' entry determines the smallest unit of time to display can be long or - * ` short form of the units, e.g. 'hour' or 'h' (default value: s) - * if int passed, it add modifiers: - * Possible values: - * - CarbonInterface::DIFF_ABSOLUTE no modifiers - * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier - * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier - * Default value: CarbonInterface::DIFF_ABSOLUTE - * @param bool $short displays short format of time units - * @param int $parts maximum number of parts to display (default value: 1: single unit) - * @param int $options human diff options - * - * @return string + * @param Carbon|DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * ⦿ 'syntax' entry (see below) + * ⦿ 'short' entry (see below) + * ⦿ 'parts' entry (see below) + * ⦿ 'options' entry (see below) + * ⦿ 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * ⦿ 'aUnit' entry, prefer "an hour" over "1 hour" if true + * ⦿ 'altNumbers' entry, use alternative numbers if available + * ` (from the current language if true is passed, from the given language(s) + * ` if array or string is passed) + * ⦿ 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * ⦿ 'other' entry (see above) + * ⦿ 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * ⦿ 'locale' language in which the diff should be output (has no effect if 'translator' key is set) + * ⦿ 'translator' a custom translator to use to translator the output. + * if int passed, it adds modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options */ - public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null); + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null): string; /** - * Get the difference in days rounded down. + * Get the difference in days. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) * - * @return int + * @return float */ - public function diffInDays($date = null, $absolute = true); + public function diffInDays($date = null, bool $absolute = false, bool $utc = false): float; /** - * Get the difference in days using a filter closure rounded down. + * Get the difference in days using a filter closure. * * @param Closure $callback * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date @@ -1361,20 +1561,20 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return int */ - public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true); + public function diffInDaysFiltered(Closure $callback, $date = null, bool $absolute = false): int; /** - * Get the difference in hours rounded down. + * Get the difference in hours. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * - * @return int + * @return float */ - public function diffInHours($date = null, $absolute = true); + public function diffInHours($date = null, bool $absolute = false): float; /** - * Get the difference in hours using a filter closure rounded down. + * Get the difference in hours using a filter closure. * * @param Closure $callback * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date @@ -1382,7 +1582,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return int */ - public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true); + public function diffInHoursFiltered(Closure $callback, $date = null, bool $absolute = false): int; /** * Get the difference in microseconds. @@ -1390,167 +1590,129 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * - * @return int + * @return float */ - public function diffInMicroseconds($date = null, $absolute = true); + public function diffInMicroseconds($date = null, bool $absolute = false): float; /** - * Get the difference in milliseconds rounded down. + * Get the difference in milliseconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function diffInMilliseconds($date = null, bool $absolute = false): float; + + /** + * Get the difference in minutes. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function diffInMinutes($date = null, bool $absolute = false): float; + + /** + * Get the difference in months. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) + * + * @return float + */ + public function diffInMonths($date = null, bool $absolute = false, bool $utc = false): float; + + /** + * Get the difference in quarters. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) + * + * @return float + */ + public function diffInQuarters($date = null, bool $absolute = false, bool $utc = false): float; + + /** + * Get the difference in seconds. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return float + */ + public function diffInSeconds($date = null, bool $absolute = false): float; + + /** + * @param Unit|string $unit microsecond, millisecond, second, minute, + * hour, day, week, month, quarter, year, + * century, millennium + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) + * + * @return float + */ + public function diffInUnit(Unit|string $unit, $date = null, bool $absolute = false, bool $utc = false): float; + + /** + * Get the difference in weekdays. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * * @return int */ - public function diffInMilliseconds($date = null, $absolute = true); + public function diffInWeekdays($date = null, bool $absolute = false): int; /** - * Get the difference in minutes rounded down. + * Get the difference in weekend days using a filter. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * * @return int */ - public function diffInMinutes($date = null, $absolute = true); + public function diffInWeekendDays($date = null, bool $absolute = false): int; /** - * Get the difference in months rounded down. + * Get the difference in weeks. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) * - * @return int + * @return float */ - public function diffInMonths($date = null, $absolute = true); - - /** - * Get the difference in quarters rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInQuarters($date = null, $absolute = true); - - /** - * Get the difference in hours rounded down using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealHours($date = null, $absolute = true); - - /** - * Get the difference in microseconds using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealMicroseconds($date = null, $absolute = true); - - /** - * Get the difference in milliseconds rounded down using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealMilliseconds($date = null, $absolute = true); - - /** - * Get the difference in minutes rounded down using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealMinutes($date = null, $absolute = true); - - /** - * Get the difference in seconds using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealSeconds($date = null, $absolute = true); - - /** - * Get the difference in seconds rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInSeconds($date = null, $absolute = true); - - /** - * Get the difference in weekdays rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInWeekdays($date = null, $absolute = true); - - /** - * Get the difference in weekend days using a filter rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInWeekendDays($date = null, $absolute = true); - - /** - * Get the difference in weeks rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInWeeks($date = null, $absolute = true); + public function diffInWeeks($date = null, bool $absolute = false, bool $utc = false): float; /** * Get the difference in years * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) * - * @return int + * @return float */ - public function diffInYears($date = null, $absolute = true); + public function diffInYears($date = null, bool $absolute = false, bool $utc = false): float; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. * You should rather use the ->settings() method. * @see settings - * - * @param int $humanDiffOption */ - public static function disableHumanDiffOption($humanDiffOption); + public static function disableHumanDiffOption(int $humanDiffOption): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. * You should rather use the ->settings() method. * @see settings - * - * @param int $humanDiffOption */ - public static function enableHumanDiffOption($humanDiffOption); + public static function enableHumanDiffOption(int $humanDiffOption): void; /** * Modify to end of current given unit. @@ -1558,16 +1720,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @example * ``` * echo Carbon::parse('2018-07-25 12:45:16.334455') - * ->startOf('month') - * ->endOf('week', Carbon::FRIDAY); + * ->startOf(Unit::Month) + * ->endOf(Unit::Week, Carbon::FRIDAY); * ``` - * - * @param string $unit - * @param array $params - * - * @return static */ - public function endOf($unit, ...$params); + public function endOf(Unit|string $unit, mixed ...$params): static; /** * Resets the date to end of the century and time to 23:59:59.999999 @@ -1612,10 +1769,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); * ``` - * - * @return static */ - public function endOfHour(); + public function endOfHour(): static; /** * Resets the date to end of the millennium and time to 23:59:59.999999 @@ -1629,6 +1784,18 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function endOfMillennium(); + /** + * Modify to end of current millisecond, microseconds such as 12345 become 123999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function endOfMillisecond(): static; + /** * Modify to end of current minute, seconds become 59 * @@ -1636,10 +1803,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); * ``` - * - * @return static */ - public function endOfMinute(); + public function endOfMinute(): static; /** * Resets the date to end of the month and time to 23:59:59.999999 @@ -1674,10 +1839,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ->endOfSecond() * ->format('H:i:s.u'); * ``` - * - * @return static */ - public function endOfSecond(); + public function endOfSecond(): static; /** * Resets the date to end of week (defined in $weekEndsAt) and time to 23:59:59.999999 @@ -1689,11 +1852,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; * ``` * - * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * @param WeekDay|int|null $weekEndsAt optional end allow you to specify the day of week to use to end the week * * @return static */ - public function endOfWeek($weekEndsAt = null); + public function endOfWeek(WeekDay|int|null $weekEndsAt = null): static; /** * Resets the date to end of the year and time to 23:59:59.999999 @@ -1717,13 +1880,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->eq('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see equalTo() - * - * @return bool */ - public function eq($date): bool; + public function eq(DateTimeInterface|string $date): bool; /** * Determines if the instance is equal to another @@ -1734,12 +1893,32 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function equalTo($date): bool; + public function equalTo(DateTimeInterface|string $date): bool; + + /** + * Return the abbreviated name of the era the current date is in, according to the "eras" of the current locale + * (empty string if the date is in none of them). + */ + public function eraAbbr(): string; + + /** + * Return the full name of the era the current date is in, according to the "eras" of the current locale + * (empty string if the date is in none of them). + */ + public function eraName(): string; + + /** + * Return the narrow name of the era the current date is in, according to the "eras" of the current locale + * (empty string if the date is in none of them). + */ + public function eraNarrow(): string; + + /** + * Return the year of the era the current date is in, according to the "eras" of the current locale + * (the calendar year if the date is in none of them). + */ + public function eraYear(): int; /** * Set the current locale to the given, execute the passed function, reset the locale to previous one, @@ -1750,7 +1929,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return mixed */ - public static function executeWithLocale($locale, $func); + public static function executeWithLocale(string $locale, callable $func): mixed; /** * Get the farthest date from the instance (second-precision). @@ -1798,187 +1977,22 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function firstOfYear($dayOfWeek = null); - /** - * Get the difference in days as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInDays($date = null, $absolute = true); - - /** - * Get the difference in hours as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInHours($date = null, $absolute = true); - - /** - * Get the difference in minutes as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInMinutes($date = null, $absolute = true); - - /** - * Get the difference in months as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInMonths($date = null, $absolute = true); - - /** - * Get the difference in days as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealDays($date = null, $absolute = true); - - /** - * Get the difference in hours as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealHours($date = null, $absolute = true); - - /** - * Get the difference in minutes as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealMinutes($date = null, $absolute = true); - - /** - * Get the difference in months as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealMonths($date = null, $absolute = true); - - /** - * Get the difference in seconds as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealSeconds($date = null, $absolute = true); - - /** - * Get the difference in weeks as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealWeeks($date = null, $absolute = true); - - /** - * Get the difference in year as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealYears($date = null, $absolute = true); - - /** - * Get the difference in seconds as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInSeconds($date = null, $absolute = true); - - /** - * Get the difference in weeks as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInWeeks($date = null, $absolute = true); - - /** - * Get the difference in year as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInYears($date = null, $absolute = true); - /** * Round the current instance second with given precision if specified. - * - * @param float|int|string|\DateInterval|null $precision - * - * @return CarbonInterface */ - public function floor($precision = 1); + public function floor(DateInterval|string|int|float $precision = 1): static; /** * Truncate the current instance at the given unit with given precision if specified. - * - * @param string $unit - * @param float|int $precision - * - * @return CarbonInterface */ - public function floorUnit($unit, $precision = 1); + public function floorUnit(string $unit, DateInterval|\Time\Duration|string|int|float $precision = 1): static; /** * Truncate the current instance week. * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week - * - * @return CarbonInterface + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week */ - public function floorWeek($weekStartsAt = null); - - /** - * Format the instance with the current locale. You can set the current - * locale using setlocale() https://php.net/setlocale. - * - * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. - * Use ->isoFormat() instead. - * Deprecated since 2.55.0 - * - * @param string $format - * - * @return string - */ - public function formatLocalized($format); + public function floorWeek(WeekDay|int|null $weekStartsAt = null): static; /** * @alias diffForHumans @@ -2049,41 +2063,57 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Create an instance from a serialized string. * - * @param string $value + * Unserializing object can lead to arbitrary execution of period filters, and assignation + * of unvalidated values to properties. + * + * You should never unserialize a value from not from a trusted source. Ensure you unserialize + * only strings coming from storage no unauthorized party can read nor write, prefer to exchange + * data with other systems via static formats such as JSON. + * + * If the data has to transit via untrusted systems before unserialization, then you should + * sign it cryptographically (with HMAC or OpenSSL for example), and verify the signature + * before unserializing it. + * + * If the content of $value is unknown, consider using the allowed_classes option to limit + * the types of objects that can be built, for instance: + * + * @example + * ```php + * $object = Carbon::fromSerialized($value, ['allowed_classes' => [Carbon::class, CarbonImmutable::class]]); + * ``` + * + * @param \Stringable|string $value + * @param array $options example: ['allowed_classes' => [CarbonImmutable::class]] * * @throws InvalidFormatException * * @return static */ - public static function fromSerialized($value); + public static function fromSerialized($value, array $options = []): static; /** * Register a custom macro. * - * @param object|callable $macro - * @param int $priority marco with higher priority is tried first + * @param callable $macro + * @param int $priority marco with higher priority is tried first * * @return void */ - public static function genericMacro($macro, $priority = 0); + public static function genericMacro(callable $macro, int $priority = 0): void; /** - * Get a part of the Carbon object - * - * @param string $name + * Get a part of the Carbon object. * * @throws UnknownGetterException * - * @return string|int|bool|DateTimeZone|null + * @return string|int|bool|DateTimeZone */ - public function get($name); + public function get(Unit|string $name): mixed; /** * Returns the alternative number for a given date property if available in the current locale. * * @param string $key date property - * - * @return string */ public function getAltNumber(string $key): string; @@ -2093,7 +2123,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return array */ - public static function getAvailableLocales(); + public static function getAvailableLocales(): array; /** * Returns list of Language object for each available locale. This object allow you to get the ISO name, native @@ -2101,114 +2131,87 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return Language[] */ - public static function getAvailableLocalesInfo(); + public static function getAvailableLocalesInfo(): array; /** * Returns list of calendar formats for ISO formatting. * * @param string|null $locale current locale used if null - * - * @return array */ - public function getCalendarFormats($locale = null); + public function getCalendarFormats(?string $locale = null): array; + + public function getClock(): ?WrapperClock; /** - * Get the days of the week - * - * @return array + * Get the days of the week. */ - public static function getDays(); + public static function getDays(): array; /** * Return the number of days since the start of the week (using the current locale or the first parameter * if explicitly given). * - * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, - * if not provided, start of week is inferred from the locale - * (Sunday for en_US, Monday for de_DE, etc.) - * - * @return int + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) */ - public function getDaysFromStartOfWeek(?int $weekStartsAt = null): int; + public function getDaysFromStartOfWeek(WeekDay|int|null $weekStartsAt = null): int; /** * Get the fallback locale. * * @see https://symfony.com/doc/current/components/translation.html#fallback-locales - * - * @return string|null */ - public static function getFallbackLocale(); + public static function getFallbackLocale(): ?string; /** * List of replacements from date() format to isoFormat(). - * - * @return array */ - public static function getFormatsToIsoReplacements(); + public static function getFormatsToIsoReplacements(): array; /** * Return default humanDiff() options (merged flags as integer). - * - * @return int */ - public static function getHumanDiffOptions(); + public static function getHumanDiffOptions(): int; /** * Returns list of locale formats for ISO formatting. * * @param string|null $locale current locale used if null - * - * @return array */ - public function getIsoFormats($locale = null); + public function getIsoFormats(?string $locale = null): array; /** * Returns list of locale units for ISO formatting. - * - * @return array */ - public static function getIsoUnits(); + public static function getIsoUnits(): array; /** * {@inheritdoc} - * - * @return array */ - #[ReturnTypeWillChange] - public static function getLastErrors(); + public static function getLastErrors(): array|false; /** * Get the raw callable macro registered globally or locally for a given name. - * - * @param string $name - * - * @return callable|null */ - public function getLocalMacro($name); + public function getLocalMacro(string $name): ?callable; /** * Get the translator of the current instance or the default if none set. - * - * @return \Symfony\Component\Translation\TranslatorInterface */ - public function getLocalTranslator(); + public function getLocalTranslator(): TranslatorInterface; /** * Get the current translator locale. * * @return string */ - public static function getLocale(); + public static function getLocale(): string; /** * Get the raw callable macro registered globally for a given name. - * - * @param string $name - * - * @return callable|null */ - public static function getMacro($name); + public static function getMacro(string $name): ?callable; /** * get midday/noon hour @@ -2224,10 +2227,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * like "-12:00". * * @param string $separator string to place between hours and minutes (":" by default) - * - * @return string */ - public function getOffsetString($separator = ':'); + public function getOffsetString(string $separator = ':'): string; /** * Returns a unit of the instance padded with 0 by default or any other string if specified. @@ -2236,10 +2237,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param int $length Length of the output (2 by default) * @param string $padString String to use for padding ("0" by default) * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) - * - * @return string */ - public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = 0); + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = 0): string; /** * Returns a timestamp rounded with the given precision (6 by default). @@ -2259,38 +2258,34 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return float */ - public function getPreciseTimestamp($precision = 6); + public function getPreciseTimestamp($precision = 6): float; /** * Returns current local settings. - * - * @return array */ - public function getSettings(); + public function getSettings(): array; /** * Get the Carbon instance (real or mock) to be returned when a "now" * instance is created. * - * @return Closure|static the current instance used for testing + * @return Closure|self|null the current instance used for testing */ - public static function getTestNow(); + public static function getTestNow(): Closure|self|null; /** * Return a format from H:i to H:i:s.u according to given unit precision. * * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" - * - * @return string */ - public static function getTimeFormatByPrecision($unitPrecision); + public static function getTimeFormatByPrecision(string $unitPrecision): string; /** * Returns the timestamp with millisecond precision. * * @return int */ - public function getTimestampMs(); + public function getTimestampMs(): int; /** * Get the translation of the current week day name (with context for languages with multiple forms). @@ -2298,19 +2293,15 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param string|null $context whole format string * @param string $keySuffix "", "_short" or "_min" * @param string|null $defaultValue default value if translation missing - * - * @return string */ - public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null); + public function getTranslatedDayName(?string $context = null, string $keySuffix = '', ?string $defaultValue = null): string; /** * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). * * @param string|null $context whole format string - * - * @return string */ - public function getTranslatedMinDayName($context = null); + public function getTranslatedMinDayName(?string $context = null): string; /** * Get the translation of the current month day name (with context for languages with multiple forms). @@ -2318,36 +2309,30 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param string|null $context whole format string * @param string $keySuffix "" or "_short" * @param string|null $defaultValue default value if translation missing - * - * @return string */ - public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null); + public function getTranslatedMonthName(?string $context = null, string $keySuffix = '', ?string $defaultValue = null): string; /** * Get the translation of the current short week day name (with context for languages with multiple forms). * * @param string|null $context whole format string - * - * @return string */ - public function getTranslatedShortDayName($context = null); + public function getTranslatedShortDayName(?string $context = null): string; /** * Get the translation of the current short month day name (with context for languages with multiple forms). * * @param string|null $context whole format string - * - * @return string */ - public function getTranslatedShortMonthName($context = null); + public function getTranslatedShortMonthName(?string $context = null): string; /** * Returns raw translation message for a given key. * - * @param string $key key to find - * @param string|null $locale current locale used if null - * @param string|null $default default value if translation returns the key - * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param TranslatorInterface $translator an optional translator to use * * @return string */ @@ -2356,42 +2341,40 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Returns raw translation message for a given key. * - * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use - * @param string $key key to find - * @param string|null $locale current locale used if null - * @param string|null $default default value if translation returns the key + * @param TranslatorInterface|null $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key * - * @return string + * @return string|Closure|null */ public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null); /** - * Get the default translator instance in use. - * - * @return \Symfony\Component\Translation\TranslatorInterface + * Initialize the default translator instance if necessary. */ - public static function getTranslator(); + public static function getTranslator(): TranslatorInterface; /** - * Get the last day of week + * Get the last day of week. + * + * @param string $locale local to consider the last day of week. * * @return int */ - public static function getWeekEndsAt(); + public static function getWeekEndsAt(?string $locale = null): int; /** - * Get the first day of week + * Get the first day of week. * * @return int */ - public static function getWeekStartsAt(); + public static function getWeekStartsAt(?string $locale = null): int; /** * Get weekend days - * - * @return array */ - public static function getWeekendDays(); + public static function getWeekendDays(): array; /** * Determines if the instance is greater (after) than another @@ -2402,12 +2385,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function greaterThan($date): bool; + public function greaterThan(DateTimeInterface|string $date): bool; /** * Determines if the instance is greater (after) than or equal to another @@ -2418,12 +2397,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function greaterThanOrEqualTo($date): bool; + public function greaterThanOrEqualTo(DateTimeInterface|string $date): bool; /** * Determines if the instance is greater (after) than another @@ -2435,13 +2410,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see greaterThan() - * - * @return bool */ - public function gt($date): bool; + public function gt(DateTimeInterface|string $date): bool; /** * Determines if the instance is greater (after) than or equal to another @@ -2453,13 +2424,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see greaterThanOrEqualTo() - * - * @return bool */ - public function gte($date): bool; + public function gte(DateTimeInterface|string $date): bool; /** * Checks if the (date)time string is in a given format. @@ -2469,13 +2436,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::hasFormat('11:12:45', 'h:i:s'); // true * Carbon::hasFormat('13:12:45', 'h:i:s'); // false * ``` - * - * @param string $date - * @param string $format - * - * @return bool */ - public static function hasFormat($date, $format); + public static function hasFormat(string $date, string $format): bool; /** * Checks if the (date)time string is in a given format. @@ -2491,23 +2453,17 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function hasFormatWithModifiers($date, $format): bool; + public static function hasFormatWithModifiers(?string $date, string $format): bool; /** * Checks if macro is registered globally or locally. - * - * @param string $name - * - * @return bool */ - public function hasLocalMacro($name); + public function hasLocalMacro(string $name): bool; /** * Return true if the current instance has its own translator. - * - * @return bool */ - public function hasLocalTranslator(); + public function hasLocalTranslator(): bool; /** * Checks if macro is registered globally. @@ -2516,16 +2472,14 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function hasMacro($name); + public static function hasMacro(string $name): bool; /** * Determine if a time string will produce a relative date. * - * @param string $time - * * @return bool true if time match a relative date, false if absolute or invalid time string */ - public static function hasRelativeKeywords($time); + public static function hasRelativeKeywords(?string $time): bool; /** * Determine if there is a valid test instance set. A valid test instance @@ -2533,16 +2487,12 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool true if there is a test instance, otherwise false */ - public static function hasTestNow(); + public static function hasTestNow(): bool; /** * Create a Carbon instance from a DateTime one. - * - * @param DateTimeInterface $date - * - * @return static */ - public static function instance($date); + public static function instance(DateTimeInterface $date): static; /** * Returns true if the current date matches the given string. @@ -2565,10 +2515,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * * @param string $tester day name, month name, hour, date, etc. as string - * - * @return bool */ - public function is(string $tester); + public function is(WeekDay|Month|string $tester): bool; /** * Determines if the instance is greater (after) than another @@ -2580,13 +2528,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see greaterThan() - * - * @return bool */ - public function isAfter($date): bool; + public function isAfter(DateTimeInterface|string $date): bool; /** * Determines if the instance is less (before) than another @@ -2598,13 +2542,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see lessThan() - * - * @return bool */ - public function isBefore($date): bool; + public function isBefore(DateTimeInterface|string $date): bool; /** * Determines if the instance is between two others @@ -2617,13 +2557,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * @param bool $equal Indicates if an equal to comparison should be done - * - * @return bool + * @param bool $equal Indicates if an equal to comparison should be done */ - public function isBetween($date1, $date2, $equal = true): bool; + public function isBetween(DateTimeInterface|string $date1, DateTimeInterface|string $date2, bool $equal = true): bool; /** * Check if its the birthday. Compares the date/month values of the two dates. @@ -2636,11 +2572,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * @param DateTimeInterface|string|null $date The instance to compare with or null to use current day. * * @return bool */ - public function isBirthday($date = null); + public function isBirthday(DateTimeInterface|string|null $date = null): bool; /** * Determines if the instance is in the current unit given. @@ -2654,10 +2590,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param string $unit The unit to test. * * @throws BadMethodCallException - * - * @return bool */ - public function isCurrentUnit($unit); + public function isCurrentUnit(string $unit): bool; /** * Checks if this day is a specific day of the week. @@ -2670,11 +2604,16 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false * ``` * - * @param int $dayOfWeek + * @param int|string $dayOfWeek * * @return bool */ - public function isDayOfWeek($dayOfWeek); + public function isDayOfWeek($dayOfWeek): bool; + + /** + * Determines if the instance is end of century (last day by default but interval can be customized). + */ + public function isEndOfCentury(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; /** * Check if the instance is end of day. @@ -2690,11 +2629,53 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false * ``` * - * @param bool $checkMicroseconds check time at microseconds precision - * - * @return bool + * @param bool $checkMicroseconds check time at microseconds precision + * @param Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval if an interval is specified it will be used as precision + * for instance with "15 minutes", it checks if current date-time + * is in the last 15 minutes of the day, with Unit::Hour, it + * checks if it's in the last hour of the day. */ - public function isEndOfDay($checkMicroseconds = false); + public function isEndOfDay(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|bool $checkMicroseconds = false, Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of decade (last day by default but interval can be customized). + */ + public function isEndOfDecade(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of hour (last microsecond by default but interval can be customized). + */ + public function isEndOfHour(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of millennium (last day by default but interval can be customized). + */ + public function isEndOfMillennium(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of millisecond (last microsecond by default but interval can be customized). + */ + public function isEndOfMillisecond(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of minute (last microsecond by default but interval can be customized). + */ + public function isEndOfMinute(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of month (last day by default but interval can be customized). + */ + public function isEndOfMonth(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of quarter (last day by default but interval can be customized). + */ + public function isEndOfQuarter(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is end of second (last microsecond by default but interval can be customized). + */ + public function isEndOfSecond(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; /** * Returns true if the date was created using CarbonImmutable::endOfTime() @@ -2703,6 +2684,33 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function isEndOfTime(): bool; + /** + * Check if the instance is end of a given unit (tolerating a given interval). + * + * @example + * ``` + * // Check if a date-time is the last 15 minutes of the hour it's in + * Carbon::parse('2019-02-28 20:13:00')->isEndOfUnit(Unit::Hour, '15 minutes'); // false + * ``` + */ + public function isEndOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null, mixed ...$params): bool; + + /** + * Determines if the instance is end of week (last day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->endOfWeek()->isEndOfWeek(); // true + * Carbon::parse('2024-08-31')->isEndOfWeek(); // false + * ``` + */ + public function isEndOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null, WeekDay|int|null $weekEndsAt = null): bool; + + /** + * Determines if the instance is end of year (last day by default but interval can be customized). + */ + public function isEndOfYear(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + /** * Determines if the instance is in the future, ie. greater (after) than now. * @@ -2711,17 +2719,13 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::now()->addHours(5)->isFuture(); // true * Carbon::now()->subHours(5)->isFuture(); // false * ``` - * - * @return bool */ - public function isFuture(); + public function isFuture(): bool; /** * Returns true if the current class/instance is immutable. - * - * @return bool */ - public static function isImmutable(); + public static function isImmutable(): bool; /** * Check if today is the last day of the Month @@ -2734,10 +2738,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-03-31')->isLastOfMonth(); // true * Carbon::parse('2019-04-30')->isLastOfMonth(); // true * ``` - * - * @return bool */ - public function isLastOfMonth(); + public function isLastOfMonth(): bool; /** * Determines if the instance is a leap year. @@ -2747,10 +2749,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2020-01-01')->isLeapYear(); // true * Carbon::parse('2019-01-01')->isLeapYear(); // false * ``` - * - * @return bool */ - public function isLeapYear(); + public function isLeapYear(): bool; /** * Determines if the instance is a long year (using ISO 8601 year). @@ -2765,10 +2765,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates - * - * @return bool */ - public function isLongIsoYear(); + public function isLongIsoYear(): bool; /** * Determines if the instance is a long year (using calendar year). @@ -2785,10 +2783,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates - * - * @return bool */ - public function isLongYear(); + public function isLongYear(): bool; /** * Check if the instance is midday. @@ -2800,10 +2796,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false * ``` - * - * @return bool */ - public function isMidday(); + public function isMidday(): bool; /** * Check if the instance is start of day / midnight. @@ -2814,10 +2808,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false * ``` - * - * @return bool */ - public function isMidnight(); + public function isMidnight(): bool; /** * Returns true if a property can be changed via setter. @@ -2826,14 +2818,36 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function isModifiableUnit($unit); + public static function isModifiableUnit($unit): bool; /** * Returns true if the current class/instance is mutable. - * - * @return bool */ - public static function isMutable(); + public static function isMutable(): bool; + + /** + * Determines if the instance is now or in the future, ie. greater (after) than or equal to now. + * + * @example + * ``` + * Carbon::now()->isNowOrFuture(); // true + * Carbon::now()->addHours(5)->isNowOrFuture(); // true + * Carbon::now()->subHours(5)->isNowOrFuture(); // false + * ``` + */ + public function isNowOrFuture(): bool; + + /** + * Determines if the instance is now or in the past, ie. less (before) than or equal to now. + * + * @example + * ``` + * Carbon::now()->isNowOrPast(); // true + * Carbon::now()->subHours(5)->isNowOrPast(); // true + * Carbon::now()->addHours(5)->isNowOrPast(); // false + * ``` + */ + public function isNowOrPast(): bool; /** * Determines if the instance is in the past, ie. less (before) than now. @@ -2843,10 +2857,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::now()->subHours(5)->isPast(); // true * Carbon::now()->addHours(5)->isPast(); // false * ``` - * - * @return bool */ - public function isPast(); + public function isPast(): bool; /** * Compares the formatted values of the two dates. @@ -2857,12 +2869,10 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false * ``` * - * @param string $format date formats to compare. - * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. - * - * @return bool + * @param string $format date formats to compare. + * @param DateTimeInterface|string $date instance to compare with or null to use current day. */ - public function isSameAs($format, $date = null); + public function isSameAs(string $format, DateTimeInterface|string $date): bool; /** * Checks if the passed in date is in the same month as the instance´s month. @@ -2875,12 +2885,12 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. - * @param bool $ofSameYear Check if it is the same month in the same year. + * @param DateTimeInterface|string $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. * * @return bool */ - public function isSameMonth($date = null, $ofSameYear = true); + public function isSameMonth(DateTimeInterface|string $date, bool $ofSameYear = true): bool; /** * Checks if the passed in date is in the same quarter as the instance quarter (and year if needed). @@ -2893,12 +2903,12 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. - * @param bool $ofSameYear Check if it is the same month in the same year. + * @param DateTimeInterface|string $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. * * @return bool */ - public function isSameQuarter($date = null, $ofSameYear = true); + public function isSameQuarter(DateTimeInterface|string $date, bool $ofSameYear = true): bool; /** * Determines if the instance is in the current unit given. @@ -2909,14 +2919,19 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false * ``` * - * @param string $unit singular unit string - * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * @param string $unit singular unit string + * @param DateTimeInterface|string $date instance to compare with or null to use current day. * * @throws BadComparisonUnitException * * @return bool */ - public function isSameUnit($unit, $date = null); + public function isSameUnit(string $unit, DateTimeInterface|string $date): bool; + + /** + * Determines if the instance is start of century (first day by default but interval can be customized). + */ + public function isStartOfCentury(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; /** * Check if the instance is start of day / midnight. @@ -2930,11 +2945,53 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false * ``` * - * @param bool $checkMicroseconds check time at microseconds precision - * - * @return bool + * @param bool $checkMicroseconds check time at microseconds precision + * @param Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval if an interval is specified it will be used as precision + * for instance with "15 minutes", it checks if current date-time + * is in the last 15 minutes of the day, with Unit::Hour, it + * checks if it's in the last hour of the day. */ - public function isStartOfDay($checkMicroseconds = false); + public function isStartOfDay(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|bool $checkMicroseconds = false, Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of decade (first day by default but interval can be customized). + */ + public function isStartOfDecade(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of hour (first microsecond by default but interval can be customized). + */ + public function isStartOfHour(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of millennium (first day by default but interval can be customized). + */ + public function isStartOfMillennium(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of millisecond (first microsecond by default but interval can be customized). + */ + public function isStartOfMillisecond(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of minute (first microsecond by default but interval can be customized). + */ + public function isStartOfMinute(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of month (first day by default but interval can be customized). + */ + public function isStartOfMonth(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of quarter (first day by default but interval can be customized). + */ + public function isStartOfQuarter(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + + /** + * Determines if the instance is start of second (first microsecond by default but interval can be customized). + */ + public function isStartOfSecond(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; /** * Returns true if the date was created using CarbonImmutable::startOfTime() @@ -2943,13 +3000,40 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function isStartOfTime(): bool; + /** + * Check if the instance is start of a given unit (tolerating a given interval). + * + * @example + * ``` + * // Check if a date-time is the first 15 minutes of the hour it's in + * Carbon::parse('2019-02-28 20:13:00')->isStartOfUnit(Unit::Hour, '15 minutes'); // true + * ``` + */ + public function isStartOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null, mixed ...$params): bool; + + /** + * Determines if the instance is start of week (first day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->startOfWeek()->isStartOfWeek(); // true + * Carbon::parse('2024-08-31')->isStartOfWeek(); // false + * ``` + */ + public function isStartOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null, WeekDay|int|null $weekStartsAt = null): bool; + + /** + * Determines if the instance is start of year (first day by default but interval can be customized). + */ + public function isStartOfYear(Unit|DateInterval|Closure|CarbonConverterInterface|\Time\Duration|string|null $interval = null): bool; + /** * Returns true if the strict mode is globally in use, false else. * (It can be overridden in specific instances.) * * @return bool */ - public static function isStrictModeEnabled(); + public static function isStrictModeEnabled(): bool; /** * Determines if the instance is today. @@ -2959,10 +3043,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::today()->isToday(); // true * Carbon::tomorrow()->isToday(); // false * ``` - * - * @return bool */ - public function isToday(); + public function isToday(): bool; /** * Determines if the instance is tomorrow. @@ -2972,10 +3054,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::tomorrow()->isTomorrow(); // true * Carbon::yesterday()->isTomorrow(); // false * ``` - * - * @return bool */ - public function isTomorrow(); + public function isTomorrow(): bool; /** * Determines if the instance is a weekday. @@ -2985,10 +3065,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-07-14')->isWeekday(); // false * Carbon::parse('2019-07-15')->isWeekday(); // true * ``` - * - * @return bool */ - public function isWeekday(); + public function isWeekday(): bool; /** * Determines if the instance is a weekend day. @@ -2998,10 +3076,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2019-07-14')->isWeekend(); // true * Carbon::parse('2019-07-15')->isWeekend(); // false * ``` - * - * @return bool */ - public function isWeekend(); + public function isWeekend(): bool; /** * Determines if the instance is yesterday. @@ -3011,18 +3087,13 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::yesterday()->isYesterday(); // true * Carbon::tomorrow()->isYesterday(); // false * ``` - * - * @return bool */ - public function isYesterday(); + public function isYesterday(): bool; /** * Format in the current language using ISO replacement patterns. * - * @param string $format * @param string|null $originalFormat provide context if a chunk has been passed alone - * - * @return string */ public function isoFormat(string $format, ?string $originalFormat = null): string; @@ -3055,11 +3126,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). * - * @param int|null $value new value for weekday if using as setter. + * @param WeekDay|int|null $value new value for weekday if using as setter. * - * @return static|int + * @return ($value is null ? int : static) */ - public function isoWeekday($value = null); + public function isoWeekday(WeekDay|int|null $value = null): static|int; /** * Get the number of weeks of the current week-year using given first day of week and first @@ -3075,11 +3146,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Prepare the object for JSON serialization. - * - * @return array|string */ - #[ReturnTypeWillChange] - public function jsonSerialize(); + public function jsonSerialize(): mixed; /** * Modify to the last occurrence of a given day of the week @@ -3126,12 +3194,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function lessThan($date): bool; + public function lessThan(DateTimeInterface|string $date): bool; /** * Determines if the instance is less (before) or equal to another @@ -3142,12 +3206,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function lessThanOrEqualTo($date): bool; + public function lessThanOrEqualTo(DateTimeInterface|string $date): bool; /** * Get/set the locale for the current instance. @@ -3155,9 +3215,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param string|null $locale * @param string ...$fallbackLocales * - * @return $this|string + * @return ($locale is null ? string : static) */ - public function locale(?string $locale = null, ...$fallbackLocales); + public function locale(?string $locale = null, string ...$fallbackLocales): static|string; /** * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). @@ -3167,7 +3227,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function localeHasDiffOneDayWords($locale); + public static function localeHasDiffOneDayWords(string $locale): bool; /** * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). @@ -3177,7 +3237,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function localeHasDiffSyntax($locale); + public static function localeHasDiffSyntax(string $locale): bool; /** * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). @@ -3187,7 +3247,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function localeHasDiffTwoDayWords($locale); + public static function localeHasDiffTwoDayWords(string $locale): bool; /** * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). @@ -3207,7 +3267,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return bool */ - public static function localeHasShortUnits($locale); + public static function localeHasShortUnits(string $locale): bool; /** * Determines if the instance is less (before) than another @@ -3219,13 +3279,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see lessThan() - * - * @return bool */ - public function lt($date): bool; + public function lt(DateTimeInterface|string $date): bool; /** * Determines if the instance is less (before) or equal to another @@ -3237,17 +3293,15 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see lessThanOrEqualTo() - * - * @return bool */ - public function lte($date): bool; + public function lte(DateTimeInterface|string $date): bool; /** * Register a custom macro. * + * Pass null macro to remove it. + * * @example * ``` * $userSettings = [ @@ -3260,12 +3314,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * echo Carbon::yesterday()->hours(11)->userFormat(); * ``` * - * @param string $name - * @param object|callable $macro - * - * @return void + * @param-closure-this static $macro */ - public static function macro($name, $macro); + public static function macro(string $name, ?callable $macro): void; /** * Make a Carbon instance from given variable if possible. @@ -3279,7 +3330,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return static|null */ - public static function make($var); + public static function make($var, DateTimeZone|string|null $timezone = null): ?static; /** * Get the maximum instance between a given instance (default now) and the current instance. @@ -3290,13 +3341,6 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function max($date = null); - /** - * Create a Carbon instance for the greatest supported date. - * - * @return static - */ - public static function maxValue(); - /** * Get the maximum instance between a given instance (default now) and the current instance. * @@ -3312,8 +3356,6 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Return the meridiem of the current time in the current locale. * * @param bool $isLower if true, returns lowercase variant if available in the current locale. - * - * @return string */ public function meridiem(bool $isLower = false): string; @@ -3333,13 +3375,6 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function min($date = null); - /** - * Create a Carbon instance for the lowest supported date. - * - * @return static - */ - public static function minValue(); - /** * Get the minimum instance between a given instance (default now) and the current instance. * @@ -3376,20 +3411,16 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * echo "$previousBlackMoon\n"; * ``` * - * @param object|string $mixin - * * @throws ReflectionException - * - * @return void */ - public static function mixin($mixin); + public static function mixin(object|string $mixin): void; /** * Calls \DateTime::modify if mutable or \DateTimeImmutable::modify else. * * @see https://php.net/manual/en/datetime.modify.php * - * @return static|false + * @return static */ #[ReturnTypeWillChange] public function modify($modify); @@ -3404,13 +3435,9 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see notEqualTo() - * - * @return bool */ - public function ne($date): bool; + public function ne(DateTimeInterface|string $date): bool; /** * Modify to the next occurrence of a given modifier such as a day of @@ -3420,7 +3447,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param string|int|null $modifier * - * @return static|false + * @return static */ public function next($modifier = null); @@ -3447,28 +3474,20 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function notEqualTo($date): bool; + public function notEqualTo(DateTimeInterface|string $date): bool; /** * Get a Carbon instance for the current date and time. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function now($tz = null); + public static function now(DateTimeZone|string|int|null $timezone = null): static; /** * Returns a present instance in the same timezone. * * @return static */ - public function nowWithSameTz(); + public function nowWithSameTz(): static; /** * Modify to the given occurrence of a given day of the week @@ -3511,11 +3530,6 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Return a property with its ordinal. - * - * @param string $key - * @param string|null $period - * - * @return string */ public function ordinal(string $key, ?string $period = null): string; @@ -3526,35 +3540,24 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * as it allows you to do Carbon::parse('Monday next week')->fn() rather * than (new Carbon('Monday next week'))->fn(). * - * @param string|DateTimeInterface|null $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException - * - * @return static */ - public static function parse($time = null, $tz = null); + public static function parse(DateTimeInterface|WeekDay|Month|string|int|float|null $time, DateTimeZone|string|int|null $timezone = null): static; /** * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). * - * @param string $time date/time string in the given language (may also contain English). - * @param string|null $locale if locale is null or not specified, current global locale will be - * used instead. - * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|int|null $timezone optional timezone for the new instance. * * @throws InvalidFormatException - * - * @return static */ - public static function parseFromLocale($time, $locale = null, $tz = null); + public static function parseFromLocale(string $time, ?string $locale = null, DateTimeZone|string|int|null $timezone = null): static; /** * Returns standardized plural of a given singular/plural unit name (in English). - * - * @param string $unit - * - * @return string */ public static function pluralUnit(string $unit): string; @@ -3566,7 +3569,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param string|int|null $modifier * - * @return static|false + * @return static */ public function previous($modifier = null); @@ -3590,10 +3593,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit * @param string|null $unit if specified, $interval must be an integer - * - * @return CarbonPeriod */ - public function range($end = null, $interval = null, $unit = null); + public function range($end = null, $interval = null, $unit = null): CarbonPeriod; /** * Call native PHP DateTime/DateTimeImmutable add() method. @@ -3602,29 +3603,25 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return static */ - public function rawAdd(DateInterval $interval); + public function rawAdd(DateInterval $interval): static; /** * Create a Carbon instance from a specific format. * - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function rawCreateFromFormat($format, $time, $tz = null); + public static function rawCreateFromFormat(string $format, string $time, $timezone = null): ?static; /** * @see https://php.net/manual/en/datetime.format.php - * - * @param string $format - * - * @return string */ - public function rawFormat($format); + public function rawFormat(string $format): string; /** * Create a carbon instance from a string. @@ -3633,28 +3630,19 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * as it allows you to do Carbon::parse('Monday next week')->fn() rather * than (new Carbon('Monday next week'))->fn(). * - * @param string|DateTimeInterface|null $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException - * - * @return static */ - public static function rawParse($time = null, $tz = null); + public static function rawParse(DateTimeInterface|WeekDay|Month|string|int|float|null $time, DateTimeZone|string|int|null $timezone = null): static; /** * Call native PHP DateTime/DateTimeImmutable sub() method. - * - * @param DateInterval $interval - * - * @return static */ - public function rawSub(DateInterval $interval); + public function rawSub(DateInterval $interval): static; /** * Remove all macros and generic macros. */ - public static function resetMacros(); + public static function resetMacros(): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -3667,14 +3655,14 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return void */ - public static function resetMonthsOverflow(); + public static function resetMonthsOverflow(): void; /** * Reset the format used to the default when type juggling a Carbon instance to a string * * @return void */ - public static function resetToStringFormat(); + public static function resetToStringFormat(): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -3687,142 +3675,99 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return void */ - public static function resetYearsOverflow(); + public static function resetYearsOverflow(): void; /** * Round the current instance second with given precision if specified. - * - * @param float|int|string|\DateInterval|null $precision - * @param string $function - * - * @return CarbonInterface */ - public function round($precision = 1, $function = 'round'); + public function round(DateInterval|string|int|float $precision = 1, callable|string $function = 'round'): static; /** * Round the current instance at the given unit with given precision if specified and the given function. - * - * @param string $unit - * @param float|int $precision - * @param string $function - * - * @return CarbonInterface */ - public function roundUnit($unit, $precision = 1, $function = 'round'); + public function roundUnit(string $unit, DateInterval|\Time\Duration|string|int|float $precision = 1, callable|string $function = 'round'): static; /** * Round the current instance week. * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week - * - * @return CarbonInterface + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week */ - public function roundWeek($weekStartsAt = null); + public function roundWeek(WeekDay|int|null $weekStartsAt = null): static; /** * The number of seconds since midnight. * - * @return int + * @return float */ - public function secondsSinceMidnight(); + public function secondsSinceMidnight(): float; /** * The number of seconds until 23:59:59. * - * @return int + * @return float */ - public function secondsUntilEndOfDay(); + public function secondsUntilEndOfDay(): float; /** * Return a serialized string of the instance. - * - * @return string */ - public function serialize(); + public function serialize(): string; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. * You should rather transform Carbon object before the serialization. * * JSON serialize all Carbon instances using the given callback. - * - * @param callable $callback - * - * @return void */ - public static function serializeUsing($callback); + public static function serializeUsing(callable|string|null $format): void; /** - * Set a part of the Carbon object - * - * @param string|array $name - * @param string|int|DateTimeZone $value + * Set one or multiple components of the Carbon object. * * @throws ImmutableException|UnknownSetterException * - * @return $this + * @return static */ - public function set($name, $value = null); + public function set(Unit|array|string $name, DateTimeZone|Month|string|int|float|null $value = null): static; + + /** + * Set current day of the instance to the passed value if it exits in the + * current month, else set current day to the last day of the month. + */ + public function setAnchorDay(int $anchorDay): static; /** * Set the date with gregorian year, month and day numbers. * * @see https://php.net/manual/en/datetime.setdate.php - * - * @param int $year - * @param int $month - * @param int $day - * - * @return static */ - #[ReturnTypeWillChange] - public function setDate($year, $month, $day); + public function setDate(int $year, int $month, int $day): static; /** * Set the year, month, and date for this instance to that of the passed instance. - * - * @param Carbon|DateTimeInterface $date now if null - * - * @return static */ - public function setDateFrom($date = null); + public function setDateFrom(DateTimeInterface|string $date): static; /** * Set the date and time all together. - * - * @param int $year - * @param int $month - * @param int $day - * @param int $hour - * @param int $minute - * @param int $second - * @param int $microseconds - * - * @return static */ - public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0); + public function setDateTime(int $year, int $month, int $day, int $hour, int $minute, int $second = 0, int $microseconds = 0): static; /** * Set the date and time for this instance to that of the passed instance. - * - * @param Carbon|DateTimeInterface $date - * - * @return static */ - public function setDateTimeFrom($date = null); + public function setDateTimeFrom(DateTimeInterface|string $date): static; /** * Set the day (keeping the current time) to the start of the week + the number of days passed as the first * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. * - * @param int $numberOfDays number of days to add after the start of the current week - * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, - * if not provided, start of week is inferred from the locale - * (Sunday for en_US, Monday for de_DE, etc.) - * - * @return static + * @param int $numberOfDays number of days to add after the start of the current week + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) */ - public function setDaysFromStartOfWeek(int $numberOfDays, ?int $weekStartsAt = null); + public function setDaysFromStartOfWeek(int $numberOfDays, WeekDay|int|null $weekStartsAt = null): static; /** * Set the fallback locale. @@ -3831,49 +3776,34 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param string $locale */ - public static function setFallbackLocale($locale); + public static function setFallbackLocale(string $locale): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. * You should rather use the ->settings() method. * @see settings - * - * @param int $humanDiffOptions */ - public static function setHumanDiffOptions($humanDiffOptions); + public static function setHumanDiffOptions(int $humanDiffOptions): void; /** * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. * * @see https://php.net/manual/en/datetime.setisodate.php - * - * @param int $year - * @param int $week - * @param int $day - * - * @return static */ - #[ReturnTypeWillChange] - public function setISODate($year, $week, $day = 1); + public function setISODate(int $year, int $week, int $day = 1): static; /** * Set the translator for the current instance. - * - * @param \Symfony\Component\Translation\TranslatorInterface $translator - * - * @return $this */ public function setLocalTranslator(TranslatorInterface $translator); /** * Set the current translator locale and indicate if the source locale file exists. - * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * Pass 'auto' as locale to use the closest language to the current LC_TIME locale. * * @param string $locale locale ex. en - * - * @return bool */ - public static function setLocale($locale); + public static function setLocale(string $locale): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -3893,16 +3823,13 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned + * instance is created. The provided instance will be returned * specifically under the following conditions: * - A call to the static now() method, ex. Carbon::now() * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') * - When a string containing the desired time is passed to Carbon::parse(). * - * Note the timezone parameter was left out of the examples above and - * has no affect as the mock value will be returned regardless of its value. - * * Only the moment is mocked with setTestNow(), the timezone will still be the one passed * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). * @@ -3913,11 +3840,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance */ - public static function setTestNow($testNow = null); + public static function setTestNow(mixed $testNow = null): void; /** * Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned + * instance is created. The provided instance will be returned * specifically under the following conditions: * - A call to the static now() method, ex. Carbon::now() * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) @@ -3934,62 +3861,36 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance */ - public static function setTestNowAndTimezone($testNow = null, $tz = null); + public static function setTestNowAndTimezone($testNow = null, $timezone = null): void; /** * Resets the current time of the DateTime object to a different time. * * @see https://php.net/manual/en/datetime.settime.php - * - * @param int $hour - * @param int $minute - * @param int $second - * @param int $microseconds - * - * @return static */ - #[ReturnTypeWillChange] - public function setTime($hour, $minute, $second = 0, $microseconds = 0); + public function setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0): static; /** * Set the hour, minute, second and microseconds for this instance to that of the passed instance. - * - * @param Carbon|DateTimeInterface $date now if null - * - * @return static */ - public function setTimeFrom($date = null); + public function setTimeFrom(DateTimeInterface|string $date): static; /** * Set the time by time string. - * - * @param string $time - * - * @return static */ - public function setTimeFromTimeString($time); + public function setTimeFromTimeString(string $time): static; /** * Set the instance's timestamp. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $unixTimestamp - * - * @return static */ - #[ReturnTypeWillChange] - public function setTimestamp($unixTimestamp); + public function setTimestamp(string|int|float $timestamp): static; /** * Set the instance's timezone from a string or object. - * - * @param DateTimeZone|string $value - * - * @return static */ - #[ReturnTypeWillChange] - public function setTimezone($value); + public function setTimezone(DateTimeZone|string|int $timeZone): static; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -4003,76 +3904,39 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return void */ - public static function setToStringFormat($format); + public static function setToStringFormat(Closure|string|null $format): void; /** * Set the default translator instance to use. * - * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @param TranslatorInterface $translator * * @return void */ - public static function setTranslator(TranslatorInterface $translator); + public static function setTranslator(TranslatorInterface $translator): void; /** * Set specified unit to new given value. * - * @param string $unit year, month, day, hour, minute, second or microsecond - * @param int $value new value for given unit - * - * @return static + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param Month|int|float|null $value new value for given unit + * @param OverflowMode|bool|null $overflow either overflow (when the day does not exist in current month) + * is allowed or not, or should use an anchor day + * @param ?int $anchorDay the day to which to go after setting year/quarter/month, + * if given but it does not exist in current month, it will + * go to the last day of the month */ - public function setUnit($unit, $value = null); + public function setUnit(string $unit, Month|int|float|null $value = null, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static; /** * Set any unit to a new value without overflowing current other unit given. * - * @param string $valueUnit unit name to modify - * @param int $value new value for the input unit - * @param string $overflowUnit unit name to not overflow - * - * @return static + * @param Unit|string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param Unit|string $overflowUnit unit name to not overflow + * @param ?int $anchorDay set day to this value after modification */ - public function setUnitNoOverflow($valueUnit, $value, $overflowUnit); - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use UTF-8 language packages on every machine. - * - * Set if UTF8 will be used for localized date/time. - * - * @param bool $utf8 - */ - public static function setUtf8($utf8); - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek - * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the - * start of week according to current locale selected and implicitly the end of week. - * - * Set the last day of week - * - * @param int|string $day week end day (or 'auto' to get the day before the first day of week - * from Carbon::getLocale() culture). - * - * @return void - */ - public static function setWeekEndsAt($day); - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the - * 'first_day_of_week' locale setting to change the start of week according to current locale - * selected and implicitly the end of week. - * - * Set the first day of week - * - * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). - * - * @return void - */ - public static function setWeekStartsAt($day); + public function setUnitNoOverflow(Unit|string $valueUnit, int $value, Unit|string $overflowUnit, ?int $anchorDay = null): static; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -4096,12 +3960,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * * Set weekend days - * - * @param array $days - * - * @return void */ - public static function setWeekendDays($days); + public static function setWeekendDays(array $days): void; /** * Set specific options. @@ -4120,30 +3980,26 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return $this|static */ - public function settings(array $settings); + public function settings(array $settings): static; /** * Set the instance's timezone from a string or object and add/subtract the offset difference. - * - * @param DateTimeZone|string $value - * - * @return static */ - public function shiftTimezone($value); + public function shiftTimezone(DateTimeZone|string $value): static; /** * Get the month overflow global behavior (can be overridden in specific instances). * * @return bool */ - public static function shouldOverflowMonths(); + public static function shouldOverflowMonths(): bool; /** * Get the month overflow global behavior (can be overridden in specific instances). * * @return bool */ - public static function shouldOverflowYears(); + public static function shouldOverflowYears(): bool; /** * @alias diffForHumans @@ -4155,29 +4011,22 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Returns standardized singular of a given singular/plural unit name (in English). - * - * @param string $unit - * - * @return string */ public static function singularUnit(string $unit): string; + public static function sleep(int|float $seconds): void; + /** * Modify to start of current given unit. * * @example * ``` * echo Carbon::parse('2018-07-25 12:45:16.334455') - * ->startOf('month') - * ->endOf('week', Carbon::FRIDAY); + * ->startOf(Unit::Month) + * ->endOf(Unit::Week, Carbon::FRIDAY); * ``` - * - * @param string $unit - * @param array $params - * - * @return static */ - public function startOf($unit, ...$params); + public function startOf(Unit|string $unit, mixed ...$params): static; /** * Resets the date to the first day of the century and the time to 00:00:00 @@ -4222,10 +4071,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); * ``` - * - * @return static */ - public function startOfHour(); + public function startOfHour(): static; /** * Resets the date to the first day of the millennium and the time to 00:00:00 @@ -4239,6 +4086,18 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable */ public function startOfMillennium(); + /** + * Modify to start of current millisecond, microseconds such as 12345 become 123000 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function startOfMillisecond(): static; + /** * Modify to start of current minute, seconds become 0 * @@ -4246,10 +4105,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); * ``` - * - * @return static */ - public function startOfMinute(); + public function startOfMinute(): static; /** * Resets the date to the first day of the month and the time to 00:00:00 @@ -4284,10 +4141,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ->startOfSecond() * ->format('H:i:s.u'); * ``` - * - * @return static */ - public function startOfSecond(); + public function startOfSecond(): static; /** * Resets the date to the first day of week (defined in $weekStartsAt) and the time to 00:00:00 @@ -4299,11 +4154,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * echo Carbon::parse('2018-07-25 12:45:16')->startOfWeek(Carbon::SUNDAY) . "\n"; * ``` * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week * * @return static */ - public function startOfWeek($weekStartsAt = null); + public function startOfWeek(WeekDay|int|null $weekStartsAt = null): static; /** * Resets the date to the first day of the year and the time to 00:00:00 @@ -4324,79 +4179,88 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @example $date->sub(15, 'days') * @example $date->sub(CarbonInterval::days(4)) * - * @param string|DateInterval|Closure|CarbonConverterInterface $unit - * @param int $value - * @param bool|null $overflow + * @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface $unit + * @param Unit|int|float|string $value + * @param OverflowMode|bool|null $overflow + * @param int|null $anchorDay * * @return static */ #[ReturnTypeWillChange] - public function sub($unit, $value = 1, $overflow = null); - - public function subRealUnit($unit, $value = 1); + public function sub($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static; /** - * Subtract given units to the current instance. + * @deprecated Prefer to use add subUTCUnit() which more accurately defines what it's doing. * - * @param string $unit - * @param int $value - * @param bool|null $overflow + * Subtract seconds to the instance using timestamp. Positive $value travels + * into the past while negative $value travels forward. + * + * @param string $unit + * @param int $value * * @return static */ - public function subUnit($unit, $value = 1, $overflow = null); + public function subRealUnit($unit, $value = 1): static; + + /** + * Subtract seconds to the instance using timestamp. Positive $value travels + * into the past while negative $value travels forward. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function subUTCUnit($unit, $value = 1): static; + + /** + * Subtract given units to the current instance. + */ + public function subUnit(Unit|string $unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static; /** * Subtract any unit to a new value without overflowing current other unit given. * - * @param string $valueUnit unit name to modify - * @param int $value amount to subtract to the input unit - * @param string $overflowUnit unit name to not overflow - * - * @return static + * @param Unit|string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param Unit|string $overflowUnit unit name to not overflow + * @param ?int $anchorDay set day to this value after modification */ - public function subUnitNoOverflow($valueUnit, $value, $overflowUnit); + public function subUnitNoOverflow(Unit|string $valueUnit, int $value, Unit|string $overflowUnit, ?int $anchorDay = null): static; /** * Subtract given units or interval to the current instance. * * @see sub() * - * @param string|DateInterval $unit - * @param int $value - * @param bool|null $overflow + * @param Unit|int|string|DateInterval $unit + * @param Unit|int|float|string $value + * @param OverflowMode|bool|null $overflow + * @param int|null $anchorDay * * @return static */ - public function subtract($unit, $value = 1, $overflow = null); + public function subtract($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static; /** - * Get the difference in a human readable format in the current locale from current instance to an other + * Get the difference in a human-readable format in the current locale from current instance to another * instance given (or now if null given). * * @return string */ - public function timespan($other = null, $timezone = null); + public function timespan($other = null, $timezone = null): string; /** * Set the instance's timestamp. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $unixTimestamp - * - * @return static */ - public function timestamp($unixTimestamp); + public function timestamp(string|int|float $timestamp): static; /** * @alias setTimezone - * - * @param DateTimeZone|string $value - * - * @return static */ - public function timezone($value); + public function timezone(DateTimeZone|string|int $value): static; /** * Get the difference in a human readable format in the current locale from an other @@ -4455,10 +4319,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * var_dump(Carbon::now()->toArray()); * ``` - * - * @return array */ - public function toArray(); + public function toArray(): array; /** * Format the instance as ATOM @@ -4467,10 +4329,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toAtomString(); * ``` - * - * @return string */ - public function toAtomString(); + public function toAtomString(): string; /** * Format the instance as COOKIE @@ -4479,10 +4339,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toCookieString(); * ``` - * - * @return string */ - public function toCookieString(); + public function toCookieString(): string; /** * @alias toDateTime @@ -4493,10 +4351,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * var_dump(Carbon::now()->toDate()); * ``` - * - * @return DateTime */ - public function toDate(); + public function toDate(): DateTime; /** * Format the instance as date @@ -4505,10 +4361,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toDateString(); * ``` - * - * @return string */ - public function toDateString(); + public function toDateString(): string; /** * Return native DateTime PHP object matching the current instance. @@ -4517,10 +4371,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * var_dump(Carbon::now()->toDateTime()); * ``` - * - * @return DateTime */ - public function toDateTime(); + public function toDateTime(): DateTime; /** * Return native toDateTimeImmutable PHP object matching the current instance. @@ -4529,10 +4381,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * var_dump(Carbon::now()->toDateTimeImmutable()); * ``` - * - * @return DateTimeImmutable */ - public function toDateTimeImmutable(); + public function toDateTimeImmutable(): DateTimeImmutable; /** * Format the instance as date and time T-separated with no timezone @@ -4543,12 +4393,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * echo "\n"; * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toDateTimeLocalString($unitPrecision = 'second'); + public function toDateTimeLocalString(string $unitPrecision = 'second'): string; /** * Format the instance as date and time @@ -4557,12 +4403,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toDateTimeString(); * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toDateTimeString($unitPrecision = 'second'); + public function toDateTimeString(string $unitPrecision = 'second'): string; /** * Format the instance with day, date and time @@ -4571,10 +4413,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toDayDateTimeString(); * ``` - * - * @return string */ - public function toDayDateTimeString(); + public function toDayDateTimeString(): string; /** * Format the instance as a readable date @@ -4583,10 +4423,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toFormattedDateString(); * ``` - * - * @return string */ - public function toFormattedDateString(); + public function toFormattedDateString(): string; /** * Format the instance with the day, and a readable date @@ -4595,8 +4433,6 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toFormattedDayDateString(); * ``` - * - * @return string */ public function toFormattedDayDateString(): string; @@ -4611,17 +4447,13 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. - * - * @return null|string */ - public function toISOString($keepOffset = false); + public function toISOString(bool $keepOffset = false): ?string; /** - * Return a immutable copy of the instance. - * - * @return CarbonImmutable + * Return an immutable copy of the instance. */ - public function toImmutable(); + public function toImmutable(): CarbonImmutable; /** * Format the instance as ISO8601 @@ -4630,10 +4462,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toIso8601String(); * ``` - * - * @return string */ - public function toIso8601String(); + public function toIso8601String(): string; /** * Convert the instance to UTC and return as Zulu ISO8601 @@ -4642,12 +4472,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toIso8601ZuluString(); * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toIso8601ZuluString($unitPrecision = 'second'); + public function toIso8601ZuluString(string $unitPrecision = 'second'): string; /** * Return the ISO-8601 string (ex: 1977-04-22T06:00:00Z) with UTC timezone. @@ -4656,17 +4482,13 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now('America/Toronto')->toJSON(); * ``` - * - * @return null|string */ - public function toJSON(); + public function toJSON(): ?string; /** * Return a mutable copy of the instance. - * - * @return Carbon */ - public function toMutable(); + public function toMutable(): Carbon; /** * Get the difference in a human readable format in the current locale from an other @@ -4705,10 +4527,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * var_dump(Carbon::now()->toObject()); * ``` - * - * @return object */ - public function toObject(); + public function toObject(): object; /** * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). @@ -4716,10 +4536,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit * @param string|null $unit if specified, $interval must be an integer - * - * @return CarbonPeriod */ - public function toPeriod($end = null, $interval = null, $unit = null); + public function toPeriod($end = null, $interval = null, $unit = null): CarbonPeriod; /** * Format the instance as RFC1036 @@ -4728,10 +4546,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRfc1036String(); * ``` - * - * @return string */ - public function toRfc1036String(); + public function toRfc1036String(): string; /** * Format the instance as RFC1123 @@ -4740,10 +4556,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRfc1123String(); * ``` - * - * @return string */ - public function toRfc1123String(); + public function toRfc1123String(): string; /** * Format the instance as RFC2822 @@ -4752,25 +4566,19 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRfc2822String(); * ``` - * - * @return string */ - public function toRfc2822String(); + public function toRfc2822String(): string; /** - * Format the instance as RFC3339 - * - * @param bool $extended + * Format the instance as RFC3339. * * @example * ``` * echo Carbon::now()->toRfc3339String() . "\n"; * echo Carbon::now()->toRfc3339String(true) . "\n"; * ``` - * - * @return string */ - public function toRfc3339String($extended = false); + public function toRfc3339String(bool $extended = false): string; /** * Format the instance as RFC7231 @@ -4779,10 +4587,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRfc7231String(); * ``` - * - * @return string */ - public function toRfc7231String(); + public function toRfc7231String(): string; /** * Format the instance as RFC822 @@ -4791,10 +4597,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRfc822String(); * ``` - * - * @return string */ - public function toRfc822String(); + public function toRfc822String(): string; /** * Format the instance as RFC850 @@ -4803,10 +4607,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRfc850String(); * ``` - * - * @return string */ - public function toRfc850String(); + public function toRfc850String(): string; /** * Format the instance as RSS @@ -4815,22 +4617,18 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toRssString(); * ``` - * - * @return string */ - public function toRssString(); + public function toRssString(): string; /** - * Returns english human readable complete date string. + * Returns english human-readable complete date string. * * @example * ``` * echo Carbon::now()->toString(); * ``` - * - * @return string */ - public function toString(); + public function toString(): string; /** * Format the instance as time @@ -4839,12 +4637,8 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toTimeString(); * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toTimeString($unitPrecision = 'second'); + public function toTimeString(string $unitPrecision = 'second'): string; /** * Format the instance as W3C @@ -4853,41 +4647,31 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * ``` * echo Carbon::now()->toW3cString(); * ``` - * - * @return string */ - public function toW3cString(); + public function toW3cString(): string; /** * Create a Carbon instance for today. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function today($tz = null); + public static function today(DateTimeZone|string|int|null $timezone = null): static; /** * Create a Carbon instance for tomorrow. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function tomorrow($tz = null); + public static function tomorrow(DateTimeZone|string|int|null $timezone = null): static; /** * Translate using translation string or callback available. * - * @param string $key - * @param array $parameters - * @param string|int|float|null $number - * @param \Symfony\Component\Translation\TranslatorInterface|null $translator - * @param bool $altNumbers + * @param string $key key to find + * @param array $parameters replacement parameters + * @param string|int|float|null $number number if plural + * @param TranslatorInterface|null $translator an optional translator to use + * @param bool $altNumbers pass true to use alternative numbers * * @return string */ - public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string; + public function translate(string $key, array $parameters = [], string|int|float|null $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string; /** * Returns the alternative number for a given integer if available in the current locale. @@ -4914,25 +4698,25 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return string */ - public static function translateTimeString($timeString, $from = null, $to = null, $mode = self::TRANSLATE_ALL); + public static function translateTimeString(string $timeString, ?string $from = null, ?string $to = null, int $mode = self::TRANSLATE_ALL): string; /** - * Translate a time string from the current locale (`$date->locale()`) to an other. + * Translate a time string from the current locale (`$date->locale()`) to another one. * * @param string $timeString time string to translate * @param string|null $to output locale of the result returned ("en" by default) * * @return string */ - public function translateTimeStringTo($timeString, $to = null); + public function translateTimeStringTo(string $timeString, ?string $to = null): string; /** * Translate using translation string or callback available. * - * @param \Symfony\Component\Translation\TranslatorInterface $translator - * @param string $key - * @param array $parameters - * @param null $number + * @param TranslatorInterface $translator an optional translator to use + * @param string $key key to find + * @param array $parameters replacement parameters + * @param int|float|null $number number if plural * * @return string */ @@ -4941,21 +4725,15 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) * but translate words whenever possible (months, day names, etc.) using the current locale. - * - * @param string $format - * - * @return string */ public function translatedFormat(string $format): string; /** * Set the timezone or returns the timezone name if no arguments passed. * - * @param DateTimeZone|string $value - * - * @return static|string + * @return ($value is null ? string : static) */ - public function tz($value = null); + public function tz(DateTimeZone|string|int|null $value = null): static|string; /** * @alias getTimestamp @@ -4964,7 +4742,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return int */ - public function unix(); + public function unix(): int; /** * @alias to @@ -5015,7 +4793,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return void */ - public static function useMonthsOverflow($monthsOverflow = true); + public static function useMonthsOverflow(bool $monthsOverflow = true): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -5026,7 +4804,7 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @param bool $strictModeEnabled */ - public static function useStrictMode($strictModeEnabled = true); + public static function useStrictMode(bool $strictModeEnabled = true): void; /** * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. @@ -5041,30 +4819,26 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return void */ - public static function useYearsOverflow($yearsOverflow = true); + public static function useYearsOverflow(bool $yearsOverflow = true): void; /** * Set the instance's timezone to UTC. - * - * @return static */ - public function utc(); + public function utc(): static; /** * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. * - * @param int|null $minuteOffset - * - * @return int|static + * @return ($minuteOffset is null ? int : static) */ - public function utcOffset(?int $minuteOffset = null); + public function utcOffset(?int $minuteOffset = null): static|int; /** * Returns the milliseconds timestamps used amongst other by Date javascript objects. * * @return float */ - public function valueOf(); + public function valueOf(): float; /** * Get/set the week number using given first day of week and first @@ -5095,11 +4869,11 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable /** * Get/set the weekday from 0 (Sunday) to 6 (Saturday). * - * @param int|null $value new value for weekday if using as setter. + * @param WeekDay|int|null $value new value for weekday if using as setter. * - * @return static|int + * @return ($value is null ? int : static) */ - public function weekday($value = null); + public function weekday(WeekDay|int|null $value = null): static|int; /** * Get the number of weeks of the current week-year using given first day of week and first @@ -5127,16 +4901,12 @@ interface CarbonInterface extends DateTimeInterface, JsonSerializable * * @return T */ - public static function withTestNow($testNow, $callback); + public static function withTestNow(mixed $testNow, callable $callback): mixed; /** * Create a Carbon instance for yesterday. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function yesterday($tz = null); + public static function yesterday(DateTimeZone|string|int|null $timezone = null): static; // } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php index 8437c545e..da463c05e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonInterval.php @@ -1,5 +1,7 @@ seconds(34)->microseconds(567_890)->seconds = 34) + * @property int $milliseconds Milliseconds component of the current interval. (CarbonInterval::seconds(34)->microseconds(567_890)->milliseconds = 567) + * @property int $microseconds Microseconds component of the current interval. (CarbonInterval::seconds(34)->microseconds(567_890)->microseconds = 567_890) * @property int $microExcludeMilli Remaining microseconds without the milliseconds. * @property int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7). * @property int $daysExcludeWeeks alias of dayzExcludeWeeks - * @property-read float $totalYears Number of years equivalent to the interval. - * @property-read float $totalMonths Number of months equivalent to the interval. - * @property-read float $totalWeeks Number of weeks equivalent to the interval. - * @property-read float $totalDays Number of days equivalent to the interval. + * @property-read float $totalYears Number of years equivalent to the interval. (For P1Y6M, the value will be 1.5) + * @property-read float $totalMonths Number of months equivalent to the interval. (For P1Y6M10D, the value will be ~12.357) + * @property-read float $totalWeeks Number of weeks equivalent to the interval. (For P6M17DT20H, the value will be ~26.548) + * @property-read float $totalDays Number of days equivalent to the interval. (For P17DT20H, the value will be ~17.833) * @property-read float $totalDayz Alias for totalDays. - * @property-read float $totalHours Number of hours equivalent to the interval. - * @property-read float $totalMinutes Number of minutes equivalent to the interval. - * @property-read float $totalSeconds Number of seconds equivalent to the interval. - * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval. - * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval. + * @property-read float $totalHours Number of hours equivalent to the interval. (For P1DT20H5M, the value will be ~44.083) + * @property-read float $totalMinutes Number of minutes equivalent to the interval. (For PT20H5M30S, the value will be 1205.5) + * @property-read float $totalSeconds Number of seconds equivalent to the interval. (CarbonInterval::minutes(2)->seconds(34)->microseconds(567_890)->totalSeconds = 154.567_890) + * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval. (CarbonInterval::seconds(34)->microseconds(567_890)->totalMilliseconds = 34567.890) + * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval. (CarbonInterval::seconds(34)->microseconds(567_890)->totalMicroseconds = 34567890) * @property-read string $locale locale of the current instance * * @method static CarbonInterval years($years = 1) Create instance specifying a number of years or modify the number of years if called on an instance. @@ -186,8 +193,9 @@ use Throwable; * @method $this ceilMicrosecond(int|float $precision = 1) Ceil the current instance microsecond with given precision. * @method $this ceilMicroseconds(int|float $precision = 1) Ceil the current instance microsecond with given precision. */ -class CarbonInterval extends DateInterval implements CarbonConverterInterface +class CarbonInterval extends DateInterval implements CarbonConverterInterface, UnitValue { + use LocalFactory; use IntervalRounding; use IntervalStep; use MagicParameter; @@ -197,6 +205,16 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface use Options; use ToStringFormat; + /** + * Unlimited parts for forHumans() method. + * + * INF constant can be used instead. + */ + public const NO_LIMIT = -1; + + public const POSITIVE = 1; + public const NEGATIVE = -1; + /** * Interval spec period designators */ @@ -209,22 +227,22 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface public const PERIOD_MINUTES = 'M'; public const PERIOD_SECONDS = 'S'; - /** - * A translator to ... er ... translate stuff - * - * @var \Symfony\Component\Translation\TranslatorInterface - */ - protected static $translator; + public const SPECIAL_TRANSLATIONS = [ + 1 => [ + 'option' => CarbonInterface::ONE_DAY_WORDS, + 'future' => 'diff_tomorrow', + 'past' => 'diff_yesterday', + ], + 2 => [ + 'option' => CarbonInterface::TWO_DAY_WORDS, + 'future' => 'diff_after_tomorrow', + 'past' => 'diff_before_yesterday', + ], + ]; - /** - * @var array|null - */ - protected static $cascadeFactors; + protected static ?array $cascadeFactors = null; - /** - * @var array - */ - protected static $formats = [ + protected static array $formats = [ 'y' => 'y', 'Y' => 'y', 'o' => 'y', @@ -244,56 +262,93 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface 'v' => 'milli', ]; - /** - * @var array|null - */ - private static $flipCascadeFactors; + private static ?array $flipCascadeFactors = null; - /** - * @var bool - */ - private static $floatSettersEnabled = false; + private static bool $floatSettersEnabled = false; /** * The registered macros. - * - * @var array */ - protected static $macros = []; + protected static array $macros = []; /** * Timezone handler for settings() method. - * - * @var mixed */ - protected $tzName; + protected DateTimeZone|string|int|null $timezoneSetting = null; + + /** + * The input used to create the interval. + */ + protected mixed $originalInput = null; + + /** + * Start date if interval was created from a difference between 2 dates. + */ + protected ?CarbonInterface $startDate = null; + + /** + * End date if interval was created from a difference between 2 dates. + */ + protected ?CarbonInterface $endDate = null; + + /** + * End date if interval was created from a difference between 2 dates. + */ + protected ?DateInterval $rawInterval = null; + + /** + * Flag if the interval was made from a diff with absolute flag on. + */ + protected bool $absolute = false; + + protected ?array $initialValues = null; /** * Set the instance's timezone from a string or object. - * - * @param \DateTimeZone|string $tzName - * - * @return static */ - public function setTimezone($tzName) + public function setTimezone(DateTimeZone|string|int $timezone): static { - $this->tzName = $tzName; + $this->timezoneSetting = $timezone; + $this->checkStartAndEnd(); + + if ($this->startDate) { + $this->startDate = $this->startDate + ->avoidMutation() + ->setTimezone($timezone); + $this->rawInterval = null; + } + + if ($this->endDate) { + $this->endDate = $this->endDate + ->avoidMutation() + ->setTimezone($timezone); + $this->rawInterval = null; + } return $this; } /** - * @internal - * * Set the instance's timezone from a string or object and add/subtract the offset difference. - * - * @param \DateTimeZone|string $tzName - * - * @return static */ - public function shiftTimezone($tzName) + public function shiftTimezone(DateTimeZone|string|int $timezone): static { - $this->tzName = $tzName; + $this->timezoneSetting = $timezone; + $this->checkStartAndEnd(); + + if ($this->startDate) { + $this->startDate = $this->startDate + ->avoidMutation() + ->shiftTimezone($timezone); + $this->rawInterval = null; + } + + if ($this->endDate) { + $this->endDate = $this->endDate + ->avoidMutation() + ->shiftTimezone($timezone); + $this->rawInterval = null; + } return $this; } @@ -302,10 +357,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * Mapping of units and factors for cascading. * * Should only be modified by changing the factors or referenced constants. - * - * @return array */ - public static function getCascadeFactors() + public static function getCascadeFactors(): array { return static::$cascadeFactors ?: static::getDefaultCascadeFactors(); } @@ -313,37 +366,17 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface protected static function getDefaultCascadeFactors(): array { return [ - 'milliseconds' => [Carbon::MICROSECONDS_PER_MILLISECOND, 'microseconds'], - 'seconds' => [Carbon::MILLISECONDS_PER_SECOND, 'milliseconds'], - 'minutes' => [Carbon::SECONDS_PER_MINUTE, 'seconds'], - 'hours' => [Carbon::MINUTES_PER_HOUR, 'minutes'], - 'dayz' => [Carbon::HOURS_PER_DAY, 'hours'], - 'weeks' => [Carbon::DAYS_PER_WEEK, 'dayz'], - 'months' => [Carbon::WEEKS_PER_MONTH, 'weeks'], - 'years' => [Carbon::MONTHS_PER_YEAR, 'months'], + 'milliseconds' => [UnitValue::MICROSECONDS_PER_MILLISECOND, 'microseconds'], + 'seconds' => [UnitValue::MILLISECONDS_PER_SECOND, 'milliseconds'], + 'minutes' => [UnitValue::SECONDS_PER_MINUTE, 'seconds'], + 'hours' => [UnitValue::MINUTES_PER_HOUR, 'minutes'], + 'dayz' => [UnitValue::HOURS_PER_DAY, 'hours'], + 'weeks' => [UnitValue::DAYS_PER_WEEK, 'dayz'], + 'months' => [UnitValue::WEEKS_PER_MONTH, 'weeks'], + 'years' => [UnitValue::MONTHS_PER_YEAR, 'months'], ]; } - private static function standardizeUnit($unit) - { - $unit = rtrim($unit, 'sz').'s'; - - return $unit === 'days' ? 'dayz' : $unit; - } - - private static function getFlipCascadeFactors() - { - if (!self::$flipCascadeFactors) { - self::$flipCascadeFactors = []; - - foreach (static::getCascadeFactors() as $to => [$factor, $from]) { - self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor]; - } - } - - return self::$flipCascadeFactors; - } - /** * Set default cascading factors for ->cascade() method. * @@ -375,19 +408,21 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Create a new CarbonInterval instance. * - * @param Closure|DateInterval|string|int|null $years - * @param int|float|null $months - * @param int|float|null $weeks - * @param int|float|null $days - * @param int|float|null $hours - * @param int|float|null $minutes - * @param int|float|null $seconds - * @param int|float|null $microseconds + * @param Closure|DateInterval|Duration|string|int|null $years + * @param int|float|null $months + * @param int|float|null $weeks + * @param int|float|null $days + * @param int|float|null $hours + * @param int|float|null $minutes + * @param int|float|null $seconds + * @param int|float|null $microseconds * * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval. */ - public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + public function __construct($years = null, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null, $inverted = false) { + $this->originalInput = \func_num_args() === 1 ? $years : \func_get_args(); + if ($years instanceof Closure) { $this->step = $years; $years = null; @@ -401,6 +436,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface return; } + if ($years instanceof Duration) { + $inverted = ($inverted xor $years->negative); + [$hours, $minutes, $seconds, $microseconds] = DurationConverter::getDurationUnits($years); + $years = 0; + $months = 0; + $weeks = 0; + $days = 0; + } + $spec = $years; $isStringSpec = (\is_string($spec) && !preg_match('/^[\d.]/', $spec)); @@ -429,8 +473,17 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } } + if ($isStringSpec && str_starts_with($spec, '-')) { + $inverted = !$inverted; + $spec = substr($spec, 1); + } + try { parent::__construct($spec); + + if ($inverted) { + $this->invert = 1; + } } catch (Throwable $exception) { try { parent::__construct('PT0S'); @@ -464,6 +517,11 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $this->assertSafeForInteger('minute', $minutes); $seconds = (float) ($match['second'] ?? 0); $this->assertSafeForInteger('second', $seconds); + $microseconds = (int) str_pad( + substr(explode('.', $match['second'] ?? '0.0')[1] ?? '0', 0, 6), + 6, + '0', + ); } $totalDays = (($weeks * static::getDaysPerWeek()) + $days); @@ -475,6 +533,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $this->h = (int) $hours; $this->i = (int) $minutes; $this->s = (int) $seconds; + $secondFloatPart = (float) ($microseconds / UnitValue::MICROSECONDS_PER_SECOND); + $this->f = $secondFloatPart; + $intervalMicroseconds = (int) ($this->f * UnitValue::MICROSECONDS_PER_SECOND); + $intervalSeconds = $seconds - $secondFloatPart; if ( ((float) $this->y) !== $years || @@ -482,7 +544,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface ((float) $this->d) !== $totalDays || ((float) $this->h) !== $hours || ((float) $this->i) !== $minutes || - ((float) $this->s) !== $seconds + ((float) $this->s) !== $intervalSeconds || + $intervalMicroseconds !== ((int) $microseconds) ) { $this->add(static::fromString( ($years - $this->y).' years '. @@ -490,7 +553,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface ($totalDays - $this->d).' days '. ($hours - $this->h).' hours '. ($minutes - $this->i).' minutes '. - ($seconds - $this->s).' seconds ' + number_format($intervalSeconds - $this->s, 6, '.', '').' seconds '. + ($microseconds - $intervalMicroseconds).' microseconds ', )); } } catch (Throwable $secondException) { @@ -499,7 +563,22 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } if ($microseconds !== null) { - $this->f = $microseconds / Carbon::MICROSECONDS_PER_SECOND; + $this->f = $microseconds / UnitValue::MICROSECONDS_PER_SECOND; + } + + foreach (['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'] as $unit) { + $value = $$unit; + + if ( + ( + \is_int($value) + || \is_float($value) + || (\is_string($value) && preg_match('/^[\d.-]+$/', $value)) + ) + && $value < 0 + ) { + $this->set($unit, $value); + } } } @@ -548,14 +627,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } static $defaults = [ - 'month' => ['year' => Carbon::MONTHS_PER_YEAR], - 'week' => ['month' => Carbon::WEEKS_PER_MONTH], - 'day' => ['week' => Carbon::DAYS_PER_WEEK], - 'hour' => ['day' => Carbon::HOURS_PER_DAY], - 'minute' => ['hour' => Carbon::MINUTES_PER_HOUR], - 'second' => ['minute' => Carbon::SECONDS_PER_MINUTE], - 'millisecond' => ['second' => Carbon::MILLISECONDS_PER_SECOND], - 'microsecond' => ['millisecond' => Carbon::MICROSECONDS_PER_MILLISECOND], + 'month' => ['year' => UnitValue::MONTHS_PER_YEAR], + 'week' => ['month' => UnitValue::WEEKS_PER_MONTH], + 'day' => ['week' => UnitValue::DAYS_PER_WEEK], + 'hour' => ['day' => UnitValue::HOURS_PER_DAY], + 'minute' => ['hour' => UnitValue::MINUTES_PER_HOUR], + 'second' => ['minute' => UnitValue::SECONDS_PER_MINUTE], + 'millisecond' => ['second' => UnitValue::MILLISECONDS_PER_SECOND], + 'microsecond' => ['millisecond' => UnitValue::MICROSECONDS_PER_MILLISECOND], ]; return $defaults[$source][$target] ?? null; @@ -568,7 +647,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface */ public static function getDaysPerWeek() { - return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK; + return static::getFactor('dayz', 'weeks') ?: UnitValue::DAYS_PER_WEEK; } /** @@ -578,7 +657,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface */ public static function getHoursPerDay() { - return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY; + return static::getFactor('hours', 'dayz') ?: UnitValue::HOURS_PER_DAY; } /** @@ -588,7 +667,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface */ public static function getMinutesPerHour() { - return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR; + return static::getFactor('minutes', 'hours') ?: UnitValue::MINUTES_PER_HOUR; } /** @@ -598,7 +677,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface */ public static function getSecondsPerMinute() { - return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE; + return static::getFactor('seconds', 'minutes') ?: UnitValue::SECONDS_PER_MINUTE; } /** @@ -608,7 +687,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface */ public static function getMillisecondsPerSecond() { - return static::getFactor('milliseconds', 'seconds') ?: Carbon::MILLISECONDS_PER_SECOND; + return static::getFactor('milliseconds', 'seconds') ?: UnitValue::MILLISECONDS_PER_SECOND; } /** @@ -618,7 +697,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface */ public static function getMicrosecondsPerMillisecond() { - return static::getFactor('microseconds', 'milliseconds') ?: Carbon::MICROSECONDS_PER_MILLISECOND; + return static::getFactor('microseconds', 'milliseconds') ?: UnitValue::MICROSECONDS_PER_MILLISECOND; } /** @@ -640,9 +719,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return static */ - public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null) + public static function create($years = null, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null, $inverted = false) { - return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds); + return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds, $inverted); } /** @@ -660,7 +739,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return static */ - public static function createFromFormat(string $format, ?string $interval) + public static function createFromFormat(string $format, ?string $interval): static { $instance = new static(0); $length = mb_strlen($format); @@ -672,7 +751,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $interval = implode($match[1], $interval); } - $interval = $interval ?? ''; + $interval ??= ''; for ($index = 0; $index < $length; $index++) { $expected = mb_substr($format, $index, 1); @@ -685,7 +764,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } $interval = mb_substr($interval, mb_strlen($match[0])); - $instance->$unit += (int) ($match[0]); + self::incrementUnit($instance, $unit, (int) ($match[0])); continue; } @@ -695,7 +774,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface "'$expected'", $nextCharacter, 'Allowed substitutes for interval formats are '.implode(', ', array_keys(static::$formats))."\n". - 'See https://php.net/manual/en/function.date.php for their meaning' + 'See https://php.net/manual/en/function.date.php for their meaning', ); } @@ -705,21 +784,107 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface if ($interval !== '') { throw new ParseErrorException( 'end of string', - $interval + $interval, ); } return $instance; } + public static function monthWithAnchorDay(int $day): static + { + return new static(function (CarbonInterface $date, bool $negated) use ($day) { + $next = $date->day(1)->addMonths($negated ? -1 : 1); + + return $next->day(min($day, $next->daysInMonth)); + }); + } + + public static function monthNoOverflow(): static + { + return new static(static function (CarbonInterface $date, bool $negated) { + return $negated + ? $date->subMonthNoOverflow() + : $date->addMonthNoOverflow(); + }); + } + + public static function yearWithAnchorDay(int $day): static + { + return new static(function (CarbonInterface $date, bool $negated) use ($day) { + $next = $date->day(1)->addYears($negated ? -1 : 1); + + return $next->day(min($day, $next->daysInMonth)); + }); + } + + public static function yearNoOverflow(): static + { + return new static(static function (CarbonInterface $date, bool $negated) { + return $negated + ? $date->subYearNoOverflow() + : $date->addYearNoOverflow(); + }); + } + + /** + * Return the original source used to create the current interval. + * + * @return array|int|string|Closure|DateInterval|Duration|mixed|null + */ + public function original() + { + return $this->originalInput; + } + + /** + * Return the start date if interval was created from a difference between 2 dates. + * + * @return CarbonInterface|null + */ + public function start(): ?CarbonInterface + { + $this->checkStartAndEnd(); + + return $this->startDate; + } + + /** + * Return the end date if interval was created from a difference between 2 dates. + * + * @return CarbonInterface|null + */ + public function end(): ?CarbonInterface + { + $this->checkStartAndEnd(); + + return $this->endDate; + } + + /** + * Get rid of the original input, start date and end date that may be kept in memory. + * + * @return $this + */ + public function optimize(): static + { + $this->originalInput = null; + $this->startDate = null; + $this->endDate = null; + $this->rawInterval = null; + $this->absolute = false; + + return $this; + } + /** * Get a copy of the instance. * * @return static */ - public function copy() + public function copy(): static { - $date = new static(0); + $date = self::withOriginal(new static(0), $this->originalInput); $date->copyProperties($this); $date->step = $this->step; @@ -731,7 +896,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return static */ - public function clone() + public function clone(): static { return $this->copy(); } @@ -745,9 +910,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * @param string $method magic method name called * @param array $parameters parameters list * - * @return static|null + * @return static|mixed|null */ - public static function __callStatic($method, $parameters) + public static function __callStatic(string $method, array $parameters) { try { $interval = new static(0); @@ -794,7 +959,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return static */ - protected static function this() + protected static function this(): static { return end(static::$macroContextStack) ?: new static(0); } @@ -823,12 +988,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @param string $intervalDefinition * + * @throws InvalidIntervalException + * * @return static */ - public static function fromString($intervalDefinition) + public static function fromString(string $intervalDefinition): static { if (empty($intervalDefinition)) { - return new static(0); + return self::withOriginal(new static(0), $intervalDefinition); } $years = 0; @@ -841,7 +1008,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $milliseconds = 0; $microseconds = 0; - $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; + $pattern = '/(-?\d+(?:\.\d+)?)\h*([^\d\h]*)/i'; preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER); while ([$part, $value, $unit] = array_shift($parts)) { @@ -980,12 +1147,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface default: throw new InvalidIntervalException( - sprintf('Invalid part %s in definition %s', $part, $intervalDefinition) + "Invalid part $part in definition $intervalDefinition", ); } } - return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * Carbon::MICROSECONDS_PER_MILLISECOND + $microseconds); + return self::withOriginal( + new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * UnitValue::MICROSECONDS_PER_MILLISECOND + $microseconds), + $intervalDefinition, + ); } /** @@ -996,59 +1166,75 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return static */ - public static function parseFromLocale($interval, $locale = null) + public static function parseFromLocale(string $interval, ?string $locale = null): static { - return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), 'en')); + return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), CarbonInterface::DEFAULT_LOCALE)); } - private static function castIntervalToClass(DateInterval $interval, string $className, array $skip = []) + /** + * Create an interval from the difference between 2 dates. + * + * @param \Carbon\Carbon|\DateTimeInterface|mixed $start + * @param \Carbon\Carbon|\DateTimeInterface|mixed $end + * + * @return static + */ + public static function diff($start, $end = null, bool $absolute = false, array $skip = []): static { - $mainClass = DateInterval::class; + $start = self::carbonOrMake($start); + $end = self::carbonOrMake($end); + $rawInterval = $start->diffAsDateInterval($end, $absolute); + $interval = static::instance($rawInterval, $skip); - if (!is_a($className, $mainClass, true)) { - throw new InvalidCastException("$className is not a sub-class of $mainClass."); - } + $interval->absolute = $absolute; + $interval->rawInterval = $rawInterval; + $interval->startDate = $start; + $interval->endDate = $end; + $interval->initialValues = $interval->getInnerValues(); - $microseconds = $interval->f; - $instance = new $className(static::getDateIntervalSpec($interval, false, $skip)); - - if ($microseconds) { - $instance->f = $microseconds; - } - - if ($interval instanceof self && is_a($className, self::class, true)) { - self::copyStep($interval, $instance); - } - - self::copyNegativeUnits($interval, $instance); - - return $instance; + return $interval; } - private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void + /** + * Invert the interval if it's inverted. + * + * @param bool $absolute do nothing if set to false + * + * @return $this + */ + public function abs(bool $absolute = false): static { - $to->invert = $from->invert; - - foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) { - if ($from->$unit < 0) { - $to->$unit *= -1; - } + if ($absolute && $this->invert) { + $this->invert(); } + + return $this; } - private static function copyStep(self $from, self $to): void + /** + * @alias abs + * + * Invert the interval if it's inverted. + * + * @param bool $absolute do nothing if set to false + * + * @return $this + */ + public function absolute(bool $absolute = true): static { - $to->setStep($from->getStep()); + return $this->abs($absolute); } /** * Cast the current instance into the given class. * - * @param string $className The $className::instance() method will be called to cast the current object. + * @template T of DateInterval|Duration * - * @return DateInterval + * @psalm-param class-string $className The $className::instance() method will be called to cast the current object. + * + * @return T */ - public function cast(string $className) + public function cast(string $className): mixed { return self::castIntervalToClass($this, $className); } @@ -1058,14 +1244,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * DateInterval objects created from DateTime::diff() as you can't externally * set the $days field. * - * @param DateInterval $interval - * @param bool $skipCopy set to true to return the passed object - * (without copying it) if it's already of the - * current class + * @param DateInterval|Duration $interval + * @param bool $skipCopy set to true to return the passed object + * (without copying it) if it's already of the + * current class * * @return static */ - public static function instance(DateInterval $interval, array $skip = [], bool $skipCopy = false) + public static function instance(DateInterval|Duration $interval, array $skip = [], bool $skipCopy = false): static { if ($skipCopy && $interval instanceof static) { return $interval; @@ -1080,26 +1266,34 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates * and recurrences). Throw an exception for invalid format, but otherwise return null. * - * @param mixed|int|DateInterval|string|Closure|null $interval interval or number of the given $unit - * @param string|null $unit if specified, $interval must be an integer - * @param bool $skipCopy set to true to return the passed object - * (without copying it) if it's already of the - * current class + * @param DateInterval|Closure|Unit|Duration|mixed|int|string|null $interval interval or number of the given $unit + * @param Unit|string|null $unit if specified, $interval must be an integer + * @param bool $skipCopy set to true to return the passed object + * (without copying it) if it's already of the + * current class * * @return static|null */ - public static function make($interval, $unit = null, bool $skipCopy = false) + public static function make($interval, $unit = null, bool $skipCopy = false): ?self { - if ($unit) { - $interval = "$interval ".Carbon::pluralUnit($unit); + if ($interval instanceof Unit) { + $interval = $interval->interval(); } - if ($interval instanceof DateInterval) { + if ($unit instanceof Unit) { + $unit = $unit->value; + } + + if ($unit) { + $interval = "$interval $unit"; + } + + if ($interval instanceof DateInterval || $interval instanceof Duration) { return static::instance($interval, [], $skipCopy); } if ($interval instanceof Closure) { - return new static($interval); + return self::withOriginal(new static($interval), $interval); } if (!\is_string($interval)) { @@ -1109,7 +1303,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface return static::makeFromString($interval); } - protected static function makeFromString(string $interval) + protected static function makeFromString(string $interval): ?self { $interval = preg_replace('/\s+/', ' ', trim($interval)); @@ -1117,23 +1311,16 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface return new static($interval); } - if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) { + if (preg_match('/^(?:\h*-?\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) { return static::fromString($interval); } - // @codeCoverageIgnoreStart - try { - /** @var static $interval */ - $interval = static::createFromDateString($interval); - } catch (DateMalformedIntervalStringException $e) { - return null; - } - // @codeCoverageIgnoreEnd + $intervalInstance = static::createFromDateString($interval); - return !$interval || $interval->isEmpty() ? null : $interval; + return $intervalInstance->isEmpty() ? null : $intervalInstance; } - protected function resolveInterval($interval) + protected function resolveInterval($interval): ?self { if (!($interval instanceof self)) { return self::make($interval); @@ -1145,25 +1332,37 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Sets up a DateInterval from the relative parts of the string. * - * @param string $time + * @param string $datetime * * @return static * * @link https://php.net/manual/en/dateinterval.createfromdatestring.php */ - #[ReturnTypeWillChange] - public static function createFromDateString($time) + public static function createFromDateString(string $datetime): static { - $interval = @parent::createFromDateString(strtr($time, [ + $string = strtr($datetime, [ ',' => ' ', ' and ' => ' ', - ])); + ]); + $previousException = null; - if ($interval instanceof DateInterval) { + try { + $interval = parent::createFromDateString($string); + } catch (Throwable $exception) { + $interval = null; + $previousException = $exception; + } + + $interval ?: throw new InvalidFormatException( + 'Could not create interval from: '.var_export($datetime, true), + previous: $previousException, + ); + + if (!($interval instanceof static)) { $interval = static::instance($interval); } - return $interval; + return self::withOriginal($interval, $datetime); } /////////////////////////////////////////////////////////////////// @@ -1172,74 +1371,46 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Get a part of the CarbonInterval object. - * - * @param string $name - * - * @throws UnknownGetterException - * - * @return int|float|string */ - public function get($name) + public function get(Unit|string $name): int|float|string|null { + $name = Unit::toName($name); + if (str_starts_with($name, 'total')) { return $this->total(substr($name, 5)); } - switch ($name) { - case 'years': - return $this->y; + $resolvedUnit = Carbon::singularUnit(rtrim($name, 'z')); - case 'months': - return $this->m; - - case 'dayz': - return $this->d; - - case 'hours': - return $this->h; - - case 'minutes': - return $this->i; - - case 'seconds': - return $this->s; - - case 'milli': - case 'milliseconds': - return (int) (round($this->f * Carbon::MICROSECONDS_PER_SECOND) / Carbon::MICROSECONDS_PER_MILLISECOND); - - case 'micro': - case 'microseconds': - return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND); - - case 'microExcludeMilli': - return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND; - - case 'weeks': - return (int) ($this->d / (int) static::getDaysPerWeek()); - - case 'daysExcludeWeeks': - case 'dayzExcludeWeeks': - return $this->d % (int) static::getDaysPerWeek(); - - case 'locale': - return $this->getTranslatorLocale(); - - default: - throw new UnknownGetterException($name); - } + return match ($resolvedUnit) { + 'tzname', 'tz_name' => match (true) { + ($this->timezoneSetting === null) => null, + \is_string($this->timezoneSetting) => $this->timezoneSetting, + ($this->timezoneSetting instanceof DateTimeZone) => $this->timezoneSetting->getName(), + default => CarbonTimeZone::instance($this->timezoneSetting)->getName(), + }, + 'year' => $this->y, + 'month' => $this->m, + 'day' => $this->d, + 'hour' => $this->h, + 'minute' => $this->i, + 'second' => $this->s, + 'milli', 'millisecond' => (int) (round($this->f * UnitValue::MICROSECONDS_PER_SECOND) / + UnitValue::MICROSECONDS_PER_MILLISECOND), + 'micro', 'microsecond' => (int) round($this->f * UnitValue::MICROSECONDS_PER_SECOND), + 'microexcludemilli' => (int) round($this->f * UnitValue::MICROSECONDS_PER_SECOND) % + UnitValue::MICROSECONDS_PER_MILLISECOND, + 'week' => (int) ($this->d / (int) static::getDaysPerWeek()), + 'daysexcludeweek', 'dayzexcludeweek' => $this->d % (int) static::getDaysPerWeek(), + 'locale' => $this->getTranslatorLocale(), + default => throw new UnknownGetterException($name, previous: new UnknownGetterException($resolvedUnit)), + }; } /** * Get a part of the CarbonInterval object. - * - * @param string $name - * - * @throws UnknownGetterException - * - * @return int|float|string */ - public function __get($name) + public function __get(string $name): int|float|string|null { return $this->get($name); } @@ -1247,19 +1418,19 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Set a part of the CarbonInterval object. * - * @param string|array $name - * @param int $value + * @param Unit|string|array $name + * @param int $value * * @throws UnknownSetterException * * @return $this */ - public function set($name, $value = null) + public function set($name, $value = null): static { $properties = \is_array($name) ? $name : [$name => $value]; foreach ($properties as $key => $value) { - switch (Carbon::singularUnit(rtrim($key, 'z'))) { + switch (Carbon::singularUnit($key instanceof Unit ? $key->value : rtrim((string) $key, 'z'))) { case 'year': $this->checkIntegerValue($key, $value); $this->y = $value; @@ -1284,6 +1455,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface break; case 'day': + if ($value === false) { + break; + } + $this->checkIntegerValue($key, $value); $this->d = $value; $this->handleDecimalPart('day', $value, $this->d); @@ -1323,17 +1498,21 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface case 'milli': case 'millisecond': - $this->microseconds = $value * Carbon::MICROSECONDS_PER_MILLISECOND + $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND; + $this->microseconds = $value * UnitValue::MICROSECONDS_PER_MILLISECOND + $this->microseconds % UnitValue::MICROSECONDS_PER_MILLISECOND; break; case 'micro': case 'microsecond': - $this->f = $value / Carbon::MICROSECONDS_PER_SECOND; + $this->f = $value / UnitValue::MICROSECONDS_PER_SECOND; break; default: + if (str_starts_with($key, ' * ')) { + return $this->setSetting(substr($key, 3), $value); + } + if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) { throw new UnknownSetterException($key); } @@ -1353,7 +1532,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @throws UnknownSetterException */ - public function __set($name, $value) + public function __set(string $name, $value) { $this->set($name, $value); } @@ -1366,7 +1545,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return static */ - public function weeksAndDays($weeks, $days) + public function weeksAndDays(int $weeks, int $days): static { $this->dayz = ($weeks * static::getDaysPerWeek()) + $days; @@ -1378,7 +1557,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return $this->years === 0 && $this->months === 0 && @@ -1393,6 +1572,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Register a custom macro. * + * Pass null macro to remove it. + * * @example * ``` * CarbonInterval::macro('twice', function () { @@ -1401,12 +1582,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * echo CarbonInterval::hours(2)->twice(); * ``` * - * @param string $name - * @param object|callable $macro - * - * @return void + * @param-closure-this static $macro */ - public static function macro($name, $macro) + public static function macro(string $name, ?callable $macro): void { static::$macros[$name] = $macro; } @@ -1444,7 +1622,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return void */ - public static function mixin($mixin) + public static function mixin($mixin): void { static::baseMixin($mixin); } @@ -1456,7 +1634,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return bool */ - public static function hasMacro($name) + public static function hasMacro(string $name): bool { return isset(static::$macros[$name]); } @@ -1469,7 +1647,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return mixed */ - protected function callMacro($name, $parameters) + protected function callMacro(string $name, array $parameters) { $macro = static::$macros[$name]; @@ -1493,9 +1671,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @throws BadFluentSetterException|Throwable * - * @return static + * @return static|int|float|string */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters) { if (static::hasMacro($method)) { return static::bindMacroContext($this, function () use (&$method, &$parameters) { @@ -1528,7 +1706,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface return $this; } - protected function getForHumansInitialVariables($syntax, $short) + protected function getForHumansInitialVariables($syntax, $short): array { if (\is_array($syntax)) { return $syntax; @@ -1559,31 +1737,34 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return array */ - protected function getForHumansParameters($syntax = null, $short = false, $parts = -1, $options = null) + protected function getForHumansParameters($syntax = null, $short = false, $parts = self::NO_LIMIT, $options = null): array { $optionalSpace = ' '; $default = $this->getTranslationMessage('list.0') ?? $this->getTranslationMessage('list') ?? ' '; + /** @var bool|string $join */ $join = $default === '' ? '' : ' '; + /** @var bool|array|string $altNumbers */ $altNumbers = false; $aUnit = false; $minimumUnit = 's'; $skip = []; extract($this->getForHumansInitialVariables($syntax, $short)); - $skip = array_map('strtolower', array_filter((array) $skip, static function ($value) { - return \is_string($value) && $value !== ''; - })); + $skip = array_map( + static fn ($unit) => $unit instanceof Unit ? $unit->value : $unit, + (array) $skip, + ); + $skip = array_map( + 'strtolower', + array_filter($skip, static fn ($unit) => \is_string($unit) && $unit !== ''), + ); - if ($syntax === null) { - $syntax = CarbonInterface::DIFF_ABSOLUTE; - } + $syntax ??= CarbonInterface::DIFF_ABSOLUTE; - if ($parts === -1) { + if ($parts === self::NO_LIMIT) { $parts = INF; } - if ($options === null) { - $options = static::getHumanDiffOptions(); - } + $options ??= static::getHumanDiffOptions(); if ($join === false) { $join = ' '; @@ -1623,16 +1804,16 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } $glue = $join; - $join = function ($list) use ($glue) { - return implode($glue, $list); - }; + $join = static fn ($list) => implode($glue, $list); } $interpolations = [ ':optional-space' => $optionalSpace, ]; - return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip]; + $translator ??= isset($locale) ? Translator::get($locale) : null; + + return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip, $translator]; } protected static function getRoundingMethodFromOptions(int $options): ?string @@ -1657,7 +1838,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return int[] */ - public function toArray() + public function toArray(): array { return [ 'years' => $this->years, @@ -1676,7 +1857,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return int[] */ - public function getNonZeroValues() + public function getNonZeroValues(): array { return array_filter($this->toArray(), 'intval'); } @@ -1687,7 +1868,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return int[] */ - public function getValuesSequence() + public function getValuesSequence(): array { $nonZeroValues = $this->getNonZeroValues(); @@ -1731,25 +1912,30 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n"; * ``` * - * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: - * - 'syntax' entry (see below) - * - 'short' entry (see below) - * - 'parts' entry (see below) - * - 'options' entry (see below) - * - 'skip' entry, list of units to skip (array of strings or a single string, + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contain: + * ⦿ 'syntax' entry (see below) + * ⦿ 'short' entry (see below) + * ⦿ 'parts' entry (see below) + * ⦿ 'options' entry (see below) + * ⦿ 'skip' entry, list of units to skip (array of strings or a single string, * ` it can be the unit name (singular or plural) or its shortcut * ` (y, m, w, d, h, min, s, ms, µs). - * - 'aUnit' entry, prefer "an hour" over "1 hour" if true - * - 'join' entry determines how to join multiple parts of the string + * ⦿ 'aUnit' entry, prefer "an hour" over "1 hour" if true + * ⦿ 'altNumbers' entry, use alternative numbers if available + * ` (from the current language if true is passed, from the given language(s) + * ` if array or string is passed) + * ⦿ 'join' entry determines how to join multiple parts of the string * ` - if $join is a string, it's used as a joiner glue * ` - if $join is a callable/closure, it get the list of string and should return a string * ` - if $join is an array, the first item will be the default glue, and the second item * ` will be used instead of the glue for the last item * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) * ` - if $join is missing, a space will be used as glue - * - 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ⦿ 'minimumUnit' entry determines the smallest unit of time to display can be long or * ` short form of the units, e.g. 'hour' or 'h' (default value: s) - * if int passed, it add modifiers: + * ⦿ 'locale' language in which the diff should be output (has no effect if 'translator' key is set) + * ⦿ 'translator' a custom translator to use to translator the output. + * if int passed, it adds modifiers: * Possible values: * - CarbonInterface::DIFF_ABSOLUTE no modifiers * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier @@ -1763,9 +1949,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return string */ - public function forHumans($syntax = null, $short = false, $parts = -1, $options = null) + public function forHumans($syntax = null, $short = false, $parts = self::NO_LIMIT, $options = null): string { - [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip] = $this + /* @var TranslatorInterface|null $translator */ + [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit, $skip, $translator] = $this ->getForHumansParameters($syntax, $short, $parts, $options); $interval = []; @@ -1779,8 +1966,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before'); $declensionMode = null; - /** @var \Symfony\Component\Translation\Translator $translator */ - $translator = $this->getLocalTranslator(); + $translator ??= $this->getLocalTranslator(); $handleDeclensions = function ($unit, $count, $index = 0, $parts = 1) use ($interpolations, $transId, $translator, $altNumbers, $absolute, &$declensionMode) { if (!$absolute) { @@ -1825,7 +2011,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $intervalValues = $this->copy()->roundUnit( $keys[$index], 1, - $method + $method, ); $previousCount = $count; } @@ -1942,17 +2128,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface if ($parts === 1) { if ($relativeToNow && $unit === 'day') { - if ($count === 1 && $options & CarbonInterface::ONE_DAY_WORDS) { - $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday'; - $translation = $this->translate($key, $interpolations, null, $translator); + $specialTranslations = static::SPECIAL_TRANSLATIONS[$count] ?? null; - if ($translation !== $key) { - return $translation; - } - } - - if ($count === 2 && $options & CarbonInterface::TWO_DAY_WORDS) { - $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday'; + if ($specialTranslations && $options & $specialTranslations['option']) { + $key = $specialTranslations[$isFuture ? 'future' : 'past']; $translation = $this->translate($key, $interpolations, null, $translator); if ($translation !== $key) { @@ -1971,6 +2150,19 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator); } + public function format(string $format): string + { + $output = parent::format($format); + + if (!str_contains($format, '%a') || !isset($this->startDate, $this->endDate)) { + return $output; + } + + $this->rawInterval ??= $this->startDate->diffAsDateInterval($this->endDate); + + return str_replace('(unknown)', $this->rawInterval->format('%a'), $output); + } + /** * Format the instance as a string using the forHumans() function. * @@ -1978,9 +2170,11 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return string */ - public function __toString() + public function __toString(): string { - $format = $this->localToStringFormat ?? static::$toStringFormat; + $format = $this->localToStringFormat + ?? $this->getFactory()->getSettings()['toStringFormat'] + ?? null; if (!$format) { return $this->forHumans(); @@ -2003,11 +2197,59 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return DateInterval */ - public function toDateInterval() + public function toDateInterval(): DateInterval { return self::castIntervalToClass($this, DateInterval::class); } + /** + * Convert the current instance to a native {@link Duration} object (PHP >= 8.6). + * + * Only the time part (hours, minutes, seconds and fractions of second) can be + * represented as a "stop-watch" duration. Nanoseconds below the microsecond + * precision of CarbonInterval are truncated. An InvalidIntervalException is + * thrown if the interval bears any date unit (years, months, weeks or days) + * because such units cannot be unambiguously converted to elapsed time. + * + * @example + * ``` + * var_dump(CarbonInterval::hours(2)->toDuration()); + * ``` + * + * @throws InvalidIntervalException when the interval contains date units + * + * @return Duration + */ + public function toDuration(): Duration + { + return DurationConverter::createDurationFromInterval($this); + } + + /** + * Create a CarbonInterval instance from a native {@link Duration} object (PHP >= 8.6). + * + * Nanoseconds below the microsecond precision of CarbonInterval are truncated. + * A negative duration produces an interval with negative time components. + * + * @example + * ``` + * var_dump(CarbonInterval::fromDuration(Time\Duration::fromMinutes(5))); + * ``` + * + * @param Duration $duration + * + * @return static + */ + public static function fromDuration(Duration $duration): static + { + [$hours, $minutes, $seconds, $microseconds] = DurationConverter::getDurationUnits($duration); + + return self::withOriginal( + new static(0, 0, 0, 0, $hours, $minutes, $seconds, $microseconds, $duration->negative), + $duration, + ); + } + /** * Convert the interval to a CarbonPeriod. * @@ -2015,17 +2257,58 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return CarbonPeriod */ - public function toPeriod(...$params) + public function toPeriod(...$params): CarbonPeriod { - if ($this->tzName) { - $tz = \is_string($this->tzName) ? new DateTimeZone($this->tzName) : $this->tzName; + if ($this->timezoneSetting) { + $timeZone = \is_string($this->timezoneSetting) + ? new DateTimeZone($this->timezoneSetting) + : $this->timezoneSetting; - if ($tz instanceof DateTimeZone) { - array_unshift($params, $tz); + if ($timeZone instanceof DateTimeZone) { + array_unshift($params, $timeZone); } } - return CarbonPeriod::create($this, ...$params); + $class = ($params[0] ?? null) instanceof DateTime ? CarbonPeriod::class : CarbonPeriodImmutable::class; + + if ($this->step) { + $dates = array_filter($params, static fn (mixed $param) => $param instanceof DateTimeInterface); + + if (\count($dates) >= 2 && $dates[0] > $dates[1]) { + $this->invert(); + } + } + + return $class::create($this, ...$params); + } + + /** + * Decompose the current interval into + * + * @param DateInterval|Duration|Closure|Unit|mixed|int|string|null $interval interval or number of the given $unit + * @param Unit|string|null $unit if specified, $interval must be an integer + * + * @return CarbonPeriod + */ + public function stepBy($interval, Unit|string|null $unit = null): CarbonPeriod + { + $this->checkStartAndEnd(); + $start = $this->startDate ?? CarbonImmutable::make('now'); + $end = $this->endDate ?? $start->copy()->add($this); + + try { + $step = static::make($interval, $unit); + } catch (InvalidFormatException $exception) { + if ($unit || (\is_string($interval) ? preg_match('/(\s|\d)/', $interval) : !($interval instanceof Unit))) { + throw $exception; + } + + $step = static::make(1, $interval); + } + + $class = $start instanceof DateTime ? CarbonPeriod::class : CarbonPeriodImmutable::class; + + return $class::create($step, $start, $end); } /** @@ -2036,23 +2319,23 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return $this */ - public function invert($inverted = null) + public function invert($inverted = null): static { $this->invert = (\func_num_args() === 0 ? !$this->invert : $inverted) ? 1 : 0; return $this; } - protected function solveNegativeInterval() + protected function solveNegativeInterval(): static { if (!$this->isEmpty() && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 && $this->microseconds <= 0) { - $this->years *= -1; - $this->months *= -1; - $this->dayz *= -1; - $this->hours *= -1; - $this->minutes *= -1; - $this->seconds *= -1; - $this->microseconds *= -1; + $this->years *= self::NEGATIVE; + $this->months *= self::NEGATIVE; + $this->dayz *= self::NEGATIVE; + $this->hours *= self::NEGATIVE; + $this->minutes *= self::NEGATIVE; + $this->seconds *= self::NEGATIVE; + $this->microseconds *= self::NEGATIVE; $this->invert(); } @@ -2062,18 +2345,20 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Add the passed interval to the current instance. * - * @param string|DateInterval $unit - * @param int|float $value + * @param Unit|DateInterval|Duration|string $unit + * @param int|float $value * * @return $this */ - public function add($unit, $value = 1) + public function add($unit, $value = 1): static { + $this->checkNoStepIsDefined(__METHOD__); + if (is_numeric($unit)) { [$value, $unit] = [$unit, $value]; } - if (\is_string($unit) && !preg_match('/^\s*\d/', $unit)) { + if (\is_string($unit) && !preg_match('/^\s*-?\d/', $unit)) { $unit = "$value $unit"; $value = 1; } @@ -2088,7 +2373,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $interval->times($value); } - $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1; + $sign = ($this->invert === 1) !== ($interval->invert === 1) ? self::NEGATIVE : self::POSITIVE; $this->years += $interval->y * $sign; $this->months += $interval->m * $sign; $this->dayz += ($interval->days === false ? $interval->d : $interval->days) * $sign; @@ -2105,13 +2390,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Subtract the passed interval to the current instance. * - * @param string|DateInterval $unit - * @param int|float $value + * @param Unit|DateInterval|Duration|string $unit + * @param int|float $value * * @return $this */ - public function sub($unit, $value = 1) + public function sub($unit, $value = 1): static { + $this->checkNoStepIsDefined(__METHOD__); + if (is_numeric($unit)) { [$value, $unit] = [$unit, $value]; } @@ -2122,12 +2409,12 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Subtract the passed interval to the current instance. * - * @param string|DateInterval $unit - * @param int|float $value + * @param Unit|DateInterval|Duration|string $unit + * @param int|float $value * * @return $this */ - public function subtract($unit, $value = 1) + public function subtract($unit, $value = 1): static { return $this->sub($unit, $value); } @@ -2155,7 +2442,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $minutes = 0, $seconds = 0, $microseconds = 0 - ): self { + ): static { return $this->add(" $years years $months months $weeks weeks $days days $hours hours $minutes minutes $seconds seconds $microseconds microseconds @@ -2163,7 +2450,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } /** - * Add given parameters to the current interval. + * Subtract given parameters to the current interval. * * @param int $years * @param int $months @@ -2185,7 +2472,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $minutes = 0, $seconds = 0, $microseconds = 0 - ): self { + ): static { return $this->sub(" $years years $months months $weeks weeks $days days $hours hours $minutes minutes $seconds seconds $microseconds microseconds @@ -2207,8 +2494,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return $this */ - public function times($factor) + public function times($factor): static { + $this->checkNoStepIsDefined(__METHOD__); + if ($factor < 0) { $this->invert = $this->invert ? 0 : 1; $factor = -$factor; @@ -2240,12 +2529,12 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return $this */ - public function shares($divider) + public function shares($divider): static { return $this->times(1 / $divider); } - protected function copyProperties(self $interval, $ignoreSign = false) + protected function copyProperties(self $interval, $ignoreSign = false): static { $this->years = $interval->years; $this->months = $interval->months; @@ -2269,8 +2558,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return $this */ - public function multiply($factor) + public function multiply($factor): static { + $this->checkNoStepIsDefined(__METHOD__); + if ($factor < 0) { $this->invert = $this->invert ? 0 : 1; $factor = -$factor; @@ -2286,7 +2577,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface static::create($yearPart) ->microseconds(abs($this->totalMicroseconds) * $factor) ->cascade(), - true + true, ); } @@ -2297,7 +2588,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return $this */ - public function divide($divider) + public function divide($divider): static { return $this->multiply(1 / $divider); } @@ -2309,36 +2600,49 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return string */ - public static function getDateIntervalSpec(DateInterval $interval, bool $microseconds = false, array $skip = []) - { + public static function getDateIntervalSpec( + DateInterval $interval, + bool $microseconds = false, + array $skip = [], + bool $withNegatives = false, + ): string { $date = array_filter([ - static::PERIOD_YEARS => abs($interval->y), - static::PERIOD_MONTHS => abs($interval->m), - static::PERIOD_DAYS => abs($interval->d), + static::PERIOD_YEARS => $withNegatives ? $interval->y : abs($interval->y), + static::PERIOD_MONTHS => $withNegatives ? $interval->m : abs($interval->m), + static::PERIOD_DAYS => $withNegatives ? $interval->d : abs($interval->d), ]); + $skip = array_map([Unit::class, 'toNameIfUnit'], $skip); + $days = abs((int) $interval->days); + if ( - $interval->days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH && + $days >= UnitValue::DAYS_PER_WEEK * UnitValue::WEEKS_PER_MONTH && (!isset($date[static::PERIOD_YEARS]) || \count(array_intersect(['y', 'year', 'years'], $skip))) && (!isset($date[static::PERIOD_MONTHS]) || \count(array_intersect(['m', 'month', 'months'], $skip))) ) { $date = [ - static::PERIOD_DAYS => abs($interval->days), + static::PERIOD_DAYS => $withNegatives ? $interval->days : $days, ]; } - $seconds = abs($interval->s); - if ($microseconds && $interval->f > 0) { - $seconds = sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000); + $seconds = $withNegatives ? $interval->s : abs($interval->s); + + if ($microseconds && $interval->f !== 0.0) { + $seconds = number_format( + $seconds + ($withNegatives ? $interval->f : abs($interval->f)), + 6, + '.', + '', + ); } $time = array_filter([ - static::PERIOD_HOURS => abs($interval->h), - static::PERIOD_MINUTES => abs($interval->i), + static::PERIOD_HOURS => $withNegatives ? $interval->h : abs($interval->h), + static::PERIOD_MINUTES => $withNegatives ? $interval->i : abs($interval->i), static::PERIOD_SECONDS => $seconds, ]); - $specString = static::PERIOD_PREFIX; + $specString = ($withNegatives && $interval->invert ? '-' : '').static::PERIOD_PREFIX; foreach ($date as $key => $value) { $specString .= $value.$key; @@ -2346,6 +2650,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface if (\count($time) > 0) { $specString .= static::PERIOD_TIME_PREFIX; + foreach ($time as $key => $value) { $specString .= $value.$key; } @@ -2359,9 +2664,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return string */ - public function spec(bool $microseconds = false) + public function spec(bool $microseconds = false, bool $withNegatives = false): string { - return static::getDateIntervalSpec($this, $microseconds); + return static::getDateIntervalSpec($this, $microseconds, [], $withNegatives); } /** @@ -2370,22 +2675,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * @param DateInterval $first * @param DateInterval $second * - * @return int + * @return int 0, 1 or -1 */ - public static function compareDateIntervals(DateInterval $first, DateInterval $second) + public static function compareDateIntervals(DateInterval $first, DateInterval $second): int { $current = Carbon::now(); $passed = $current->avoidMutation()->add($second); $current->add($first); - if ($current < $passed) { - return -1; - } - if ($current > $passed) { - return 1; - } - - return 0; + return $current <=> $passed; } /** @@ -2393,93 +2691,19 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @param DateInterval $interval * - * @return int + * @return int 0, 1 or -1 */ - public function compare(DateInterval $interval) + public function compare(DateInterval $interval): int { return static::compareDateIntervals($this, $interval); } - private function invertCascade(array $values) - { - return $this->set(array_map(function ($value) { - return -$value; - }, $values))->doCascade(true)->invert(); - } - - private function doCascade(bool $deep) - { - $originalData = $this->toArray(); - $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond()); - $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond(); - $originalData['weeks'] = (int) ($this->d / static::getDaysPerWeek()); - $originalData['daysExcludeWeeks'] = fmod($this->d, static::getDaysPerWeek()); - unset($originalData['days']); - $newData = $originalData; - $previous = []; - - foreach (self::getFlipCascadeFactors() as $source => [$target, $factor]) { - foreach (['source', 'target'] as $key) { - if ($$key === 'dayz') { - $$key = 'daysExcludeWeeks'; - } - } - - $value = $newData[$source]; - $modulo = fmod($factor + fmod($value, $factor), $factor); - $newData[$source] = $modulo; - $newData[$target] += ($value - $modulo) / $factor; - - $decimalPart = fmod($newData[$source], 1); - - if ($decimalPart !== 0.0) { - $unit = $source; - - foreach ($previous as [$subUnit, $subFactor]) { - $newData[$unit] -= $decimalPart; - $newData[$subUnit] += $decimalPart * $subFactor; - $decimalPart = fmod($newData[$subUnit], 1); - - if ($decimalPart === 0.0) { - break; - } - - $unit = $subUnit; - } - } - - array_unshift($previous, [$source, $factor]); - } - - $positive = null; - - if (!$deep) { - foreach ($newData as $value) { - if ($value) { - if ($positive === null) { - $positive = ($value > 0); - - continue; - } - - if (($value > 0) !== $positive) { - return $this->invertCascade($originalData) - ->solveNegativeInterval(); - } - } - } - } - - return $this->set($newData) - ->solveNegativeInterval(); - } - /** * Convert overflowed values into bigger units. * * @return $this */ - public function cascade() + public function cascade(): static { return $this->doCascade(false); } @@ -2515,7 +2739,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return float */ - public function total($unit) + public function total(string $unit): float { $realUnit = $unit = strtolower($unit); @@ -2525,6 +2749,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface throw new UnknownUnitException($unit); } + $this->checkStartAndEnd(); + + if ($this->startDate && $this->endDate) { + $diff = $this->startDate->diffInUnit($unit, $this->endDate); + + return $this->absolute ? abs($diff) : $diff; + } + $result = 0; $cumulativeFactor = 0; $unitFound = false; @@ -2539,8 +2771,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface 'hours' => $this->hours, 'minutes' => $this->minutes, 'seconds' => $this->seconds, - 'milliseconds' => (int) ($this->microseconds / Carbon::MICROSECONDS_PER_MILLISECOND), - 'microseconds' => $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND, + 'milliseconds' => (int) ($this->microseconds / UnitValue::MICROSECONDS_PER_MILLISECOND), + 'microseconds' => $this->microseconds % UnitValue::MICROSECONDS_PER_MILLISECOND, ]; if (isset($factors['dayz']) && $factors['dayz'][0] !== 'weeks') { @@ -2592,7 +2824,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface } if ($this->invert) { - $result *= -1; + $result *= self::NEGATIVE; } if ($unit === 'weeks') { @@ -2628,7 +2860,25 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface { $interval = $this->resolveInterval($interval); - return $interval !== null && $this->totalMicroseconds === $interval->totalMicroseconds; + if ($interval === null) { + return false; + } + + $step = $this->getStep(); + + if ($step) { + return $step === $interval->getStep(); + } + + if ($this->isEmpty()) { + return $interval->isEmpty(); + } + + $cascadedInterval = $this->copy()->cascade(); + $comparedInterval = $interval->copy()->cascade(); + + return $cascadedInterval->invert === $comparedInterval->invert && + $cascadedInterval->getNonZeroValues() === $comparedInterval->getNonZeroValues(); } /** @@ -2786,7 +3036,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return bool */ - public function between($interval1, $interval2, $equal = true): bool + public function between($interval1, $interval2, bool $equal = true): bool { return $equal ? $this->greaterThanOrEqualTo($interval1) && $this->lessThanOrEqualTo($interval2) @@ -2850,7 +3100,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface * * @return bool */ - public function isBetween($interval1, $interval2, $equal = true): bool + public function isBetween($interval1, $interval2, bool $equal = true): bool { return $this->between($interval1, $interval2, $equal); } @@ -2858,15 +3108,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Round the current instance at the given unit with given precision if specified and the given function. * - * @param string $unit - * @param float|int|string|DateInterval|null $precision - * @param string $function - * * @throws Exception - * - * @return $this */ - public function roundUnit($unit, $precision = 1, $function = 'round') + public function roundUnit(string $unit, DateInterval|Duration|string|int|float $precision = 1, string $function = 'round'): static { if (static::getCascadeFactors() !== static::getDefaultCascadeFactors()) { $value = $function($this->total($unit) / $precision) * $precision; @@ -2877,6 +3121,12 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface )->invert($inverted)->cascade()); } + $initiallyInverted = (bool) $this->invert; + + if ($initiallyInverted) { + $this->invert(); + } + $base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC') ->roundUnit($unit, $precision, $function); $next = $base->add($this); @@ -2889,23 +3139,23 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $this->copyProperties( $next ->roundUnit($unit, $precision, $function) - ->diffAsCarbonInterval($base) + ->diff($base), ); - return $this->invert($inverted); + return $this->invert($initiallyInverted xor $inverted); } /** * Truncate the current instance at the given unit with given precision if specified. * - * @param string $unit - * @param float|int|string|DateInterval|null $precision + * @param string $unit + * @param float|int|string|DateInterval|Duration|null $precision * * @throws Exception * * @return $this */ - public function floorUnit($unit, $precision = 1) + public function floorUnit(string $unit, $precision = 1): static { return $this->roundUnit($unit, $precision, 'floor'); } @@ -2913,14 +3163,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Ceil the current instance at the given unit with given precision if specified. * - * @param string $unit - * @param float|int|string|DateInterval|null $precision + * @param string $unit + * @param float|int|string|DateInterval|Duration|null $precision * * @throws Exception * * @return $this */ - public function ceilUnit($unit, $precision = 1) + public function ceilUnit(string $unit, $precision = 1): static { return $this->roundUnit($unit, $precision, 'ceil'); } @@ -2928,14 +3178,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Round the current instance second with given precision if specified. * - * @param float|int|string|DateInterval|null $precision - * @param string $function + * @param float|int|string|DateInterval|Duration|null $precision + * @param string $function * * @throws Exception * * @return $this */ - public function round($precision = 1, $function = 'round') + public function round($precision = 1, string $function = 'round'): static { return $this->roundWith($precision, $function); } @@ -2943,13 +3193,11 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Round the current instance second with given precision if specified. * - * @param float|int|string|DateInterval|null $precision - * * @throws Exception * * @return $this */ - public function floor($precision = 1) + public function floor(DateInterval|Duration|string|float|int $precision = 1): static { return $this->round($precision, 'floor'); } @@ -2957,28 +3205,347 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Ceil the current instance second with given precision if specified. * - * @param float|int|string|DateInterval|null $precision - * * @throws Exception * * @return $this */ - public function ceil($precision = 1) + public function ceil(DateInterval|Duration|string|float|int $precision = 1): static { return $this->round($precision, 'ceil'); } - private function needsDeclension(string $mode, int $index, int $parts): bool + public function __unserialize(array $data): void { - switch ($mode) { - case 'last': - return $index === $parts - 1; - default: - return true; + $properties = array_combine( + array_map( + static fn (mixed $key) => \is_string($key) + ? str_replace('tzName', 'timezoneSetting', $key) + : $key, + array_keys($data), + ), + $data, + ); + + if (method_exists(parent::class, '__unserialize')) { + // PHP >= 8.2 + parent::__unserialize($properties); + + return; + } + + // PHP <= 8.1 + // @codeCoverageIgnoreStart + $properties = array_combine( + array_map( + static fn (string $property) => preg_replace('/^\0.+\0/', '', $property), + array_keys($data), + ), + $data, + ); + $localStrictMode = $this->localStrictModeEnabled; + $this->localStrictModeEnabled = false; + $days = $properties['days'] ?? false; + $this->days = $days === false ? false : (int) $days; + $this->y = (int) ($properties['y'] ?? 0); + $this->m = (int) ($properties['m'] ?? 0); + $this->d = (int) ($properties['d'] ?? 0); + $this->h = (int) ($properties['h'] ?? 0); + $this->i = (int) ($properties['i'] ?? 0); + $this->s = (int) ($properties['s'] ?? 0); + $this->f = (float) ($properties['f'] ?? 0.0); + // @phpstan-ignore-next-line + $this->weekday = (int) ($properties['weekday'] ?? 0); + // @phpstan-ignore-next-line + $this->weekday_behavior = (int) ($properties['weekday_behavior'] ?? 0); + // @phpstan-ignore-next-line + $this->first_last_day_of = (int) ($properties['first_last_day_of'] ?? 0); + $this->invert = (int) ($properties['invert'] ?? 0); + // @phpstan-ignore-next-line + $this->special_type = (int) ($properties['special_type'] ?? 0); + // @phpstan-ignore-next-line + $this->special_amount = (int) ($properties['special_amount'] ?? 0); + // @phpstan-ignore-next-line + $this->have_weekday_relative = (int) ($properties['have_weekday_relative'] ?? 0); + // @phpstan-ignore-next-line + $this->have_special_relative = (int) ($properties['have_special_relative'] ?? 0); + parent::__construct(self::getDateIntervalSpec($this)); + + foreach ($properties as $property => $value) { + if ($property === 'localStrictModeEnabled') { + continue; + } + + $this->$property = $value; + } + + $this->localStrictModeEnabled = $properties['localStrictModeEnabled'] ?? $localStrictMode; + // @codeCoverageIgnoreEnd + } + + /** + * @template T + * + * @param T $interval + * @param mixed $original + * + * @return T + */ + private static function withOriginal(mixed $interval, mixed $original): mixed + { + if ($interval instanceof self) { + $interval->originalInput = $original; + } + + return $interval; + } + + private static function standardizeUnit(string $unit): string + { + $unit = rtrim($unit, 'sz').'s'; + + return $unit === 'days' ? 'dayz' : $unit; + } + + private static function getFlipCascadeFactors(): array + { + if (!self::$flipCascadeFactors) { + self::$flipCascadeFactors = []; + + foreach (self::getCascadeFactors() as $to => [$factor, $from]) { + self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor]; + } + } + + return self::$flipCascadeFactors; + } + + /** + * @template T of DateInterval|Duration + * + * @param DateInterval|Duration $interval + * + * @psalm-param class-string $className + * + * @return T + */ + private static function castIntervalToClass(DateInterval|Duration $interval, string $className, array $skip = []): DateInterval|Duration + { + $mainClass = DateInterval::class; + + if ($className === Duration::class) { + return DurationConverter::createDurationFromInterval($interval); + } + + if (!is_a($className, $mainClass, true)) { + throw new InvalidCastException("$className is not a sub-class of $mainClass."); + } + + [$microseconds, $instance] = $interval instanceof Duration + ? [ + $interval->nanoseconds / 1_000_000_000, + new $className(DurationConverter::getDurationIsoSpec($interval)), + ] + : [$interval->f, self::buildInstance($interval, $className, $skip)]; + + if ($instance instanceof self) { + $instance->originalInput = $interval; + } + + if ($microseconds) { + $instance->f = $microseconds; + } + + if ($interval instanceof Duration) { + $instance->invert = (int) $interval->negative; + + return self::withOriginal($instance, $interval); + } + + if ($interval instanceof self && is_a($className, self::class, true)) { + self::copyStep($interval, $instance); + } + + self::copyNegativeUnits($interval, $instance); + + return self::withOriginal($instance, $interval); + } + + /** + * @template T of DateInterval + * + * @param DateInterval $interval + * + * @psalm-param class-string $className + * + * @return T + */ + private static function buildInstance( + DateInterval $interval, + string $className, + array $skip = [], + ): object { + $serialization = self::buildSerializationString($interval, $className, $skip); + + return match ($serialization) { + null => new $className(static::getDateIntervalSpec($interval, false, $skip)), + default => unserialize($serialization), + }; + } + + /** + * As demonstrated by rlanvin (https://github.com/rlanvin) in + * https://github.com/briannesbitt/Carbon/issues/3018#issuecomment-2888538438 + * + * Modifying the output of serialize() to change the class name and unserializing + * the tweaked string allows creating new interval instances where the ->days + * property can be set. It's not possible neither with `new` nto with `__set_state`. + * + * It has a non-negligible performance cost, so we'll use this method only if + * $interval->days !== false. + */ + private static function buildSerializationString( + DateInterval $interval, + string $className, + array $skip = [], + ): ?string { + if ($interval->days === false || PHP_VERSION_ID < 8_02_00 || $skip !== []) { + return null; + } + + // De-enhance CarbonInterval objects to be serializable back to DateInterval + if ($interval instanceof self && !is_a($className, self::class, true)) { + $interval = clone $interval; + unset($interval->timezoneSetting); + unset($interval->originalInput); + unset($interval->startDate); + unset($interval->endDate); + unset($interval->rawInterval); + unset($interval->absolute); + unset($interval->initialValues); + unset($interval->clock); + unset($interval->step); + unset($interval->localMonthsOverflow); + unset($interval->localYearsOverflow); + unset($interval->localStrictModeEnabled); + unset($interval->localHumanDiffOptions); + unset($interval->localToStringFormat); + unset($interval->localSerializer); + unset($interval->localMacros); + unset($interval->localGenericMacros); + unset($interval->localFormatFunction); + unset($interval->localTranslator); + } + + $serialization = serialize($interval); + $inputClass = $interval::class; + $expectedStart = 'O:'.\strlen($inputClass).':"'.$inputClass.'":'; + + if (!str_starts_with($serialization, $expectedStart)) { + return null; // @codeCoverageIgnore + } + + return 'O:'.\strlen($className).':"'.$className.'":'.substr($serialization, \strlen($expectedStart)); + } + + private static function copyStep(self $from, self $to): void + { + $to->setStep($from->getStep()); + } + + private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void + { + $to->invert = $from->invert; + + foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) { + if ($from->$unit < 0) { + self::setIntervalUnit($to, $unit, $to->$unit * self::NEGATIVE); + } } } - private function checkIntegerValue(string $name, $value) + private function invertCascade(array $values): static + { + return $this->set(array_map(function ($value) { + return -$value; + }, $values))->doCascade(true)->invert(); + } + + private function doCascade(bool $deep): static + { + $originalData = $this->toArray(); + $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond()); + $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond(); + $originalData['weeks'] = (int) ($this->d / static::getDaysPerWeek()); + $originalData['daysExcludeWeeks'] = fmod($this->d, static::getDaysPerWeek()); + unset($originalData['days']); + $newData = $originalData; + $previous = []; + + foreach (self::getFlipCascadeFactors() as $source => [$target, $factor]) { + foreach (['source', 'target'] as $key) { + if ($$key === 'dayz') { + $$key = 'daysExcludeWeeks'; + } + } + + $value = $newData[$source]; + $modulo = fmod($factor + fmod($value, $factor), $factor); + $newData[$source] = $modulo; + $newData[$target] += ($value - $modulo) / $factor; + + $decimalPart = fmod($newData[$source], 1); + + if ($decimalPart !== 0.0) { + $unit = $source; + + foreach ($previous as [$subUnit, $subFactor]) { + $newData[$unit] -= $decimalPart; + $newData[$subUnit] += $decimalPart * $subFactor; + $decimalPart = fmod($newData[$subUnit], 1); + + if ($decimalPart === 0.0) { + break; + } + + $unit = $subUnit; + } + } + + array_unshift($previous, [$source, $factor]); + } + + $positive = null; + + if (!$deep) { + foreach ($newData as $value) { + if ($value) { + if ($positive === null) { + $positive = ($value > 0); + + continue; + } + + if (($value > 0) !== $positive) { + return $this->invertCascade($originalData) + ->solveNegativeInterval(); + } + } + } + } + + return $this->set($newData) + ->solveNegativeInterval(); + } + + private function needsDeclension(string $mode, int $index, int $parts): bool + { + return match ($mode) { + 'last' => $index === $parts - 1, + default => true, + }; + } + + private function checkIntegerValue(string $name, mixed $value): void { if (\is_int($value)) { return; @@ -2998,7 +3565,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface "From 3.0.0, decimal part will no longer be truncated and will be cascaded to smaller units.\n". "- To maintain the current behavior, use explicit cast: $name((int) \$value)\n". "- To adopt the new behavior globally, call CarbonInterval::enableFloatSetters()\n", - \E_USER_DEPRECATED + \E_USER_DEPRECATED, ); } } @@ -3006,14 +3573,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface /** * Throw an exception if precision loss when storing the given value as an integer would be >= 1.0. */ - private function assertSafeForInteger(string $name, $value) + private function assertSafeForInteger(string $name, mixed $value): void { if ($value && !\is_int($value) && ($value >= 0x7fffffffffffffff || $value <= -0x7fffffffffffffff)) { throw new OutOfRangeException($name, -0x7fffffffffffffff, 0x7fffffffffffffff, $value); } } - private function handleDecimalPart(string $unit, $value, $integerValue) + private function handleDecimalPart(string $unit, mixed $value, mixed $integerValue): void { if (self::$floatSettersEnabled) { $floatValue = (float) $value; @@ -3051,4 +3618,123 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface $this->add($unit, $floatValue - $base); } } + + private function getInnerValues(): array + { + return [$this->y, $this->m, $this->d, $this->h, $this->i, $this->s, $this->f, $this->invert, $this->days]; + } + + private function checkStartAndEnd(): void + { + if ( + $this->initialValues !== null + && ($this->startDate !== null || $this->endDate !== null) + && $this->initialValues !== $this->getInnerValues() + ) { + $this->absolute = false; + $this->startDate = null; + $this->endDate = null; + $this->rawInterval = null; + } + } + + /** @return $this */ + private function setSetting(string $setting, mixed $value): self + { + switch ($setting) { + case 'timezoneSetting': + return $value === null ? $this : $this->setTimezone($value); + + case 'step': + $this->setStep($value); + + return $this; + + case 'localMonthsOverflow': + return $value === null ? $this : $this->settings(['monthOverflow' => $value]); + + case 'localYearsOverflow': + return $value === null ? $this : $this->settings(['yearOverflow' => $value]); + + case 'localStrictModeEnabled': + case 'localHumanDiffOptions': + case 'localToStringFormat': + case 'localSerializer': + case 'localMacros': + case 'localGenericMacros': + case 'localFormatFunction': + case 'localTranslator': + $this->$setting = $value; + + return $this; + + default: + // Drop unknown settings + return $this; + } + } + + private static function carbonOrMake(mixed $dateTime): CarbonInterface + { + return $dateTime instanceof CarbonInterface + ? $dateTime + : Carbon::make($dateTime); + } + + private static function incrementUnit(DateInterval $instance, string $unit, int $value): void + { + if ($value === 0) { + return; + } + + // @codeCoverageIgnoreStart + if (PHP_VERSION_ID !== 8_03_20) { + $instance->$unit += $value; + + return; + } + + // Cannot use +=, nor set to a negative value directly as it segfaults in PHP 8.3.20 + self::setIntervalUnit($instance, $unit, ($instance->$unit ?? 0) + $value); + // @codeCoverageIgnoreEnd + } + + /** @codeCoverageIgnore */ + private static function setIntervalUnit(DateInterval $instance, string $unit, mixed $value): void + { + switch ($unit) { + case 'y': + $instance->y = $value; + + break; + + case 'm': + $instance->m = $value; + + break; + + case 'd': + $instance->d = $value; + + break; + + case 'h': + $instance->h = $value; + + break; + + case 'i': + $instance->i = $value; + + break; + + case 's': + $instance->s = $value; + + break; + + default: + $instance->$unit = $value; + } + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php index d12a98697..e445afaa0 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriod.php @@ -1,5 +1,7 @@ rewind(); + + while ($this->valid()) { + $key = $this->key(); + $value = $this->current(); + + yield $key => $value; + + $this->next(); + } + } /** * Make a CarbonPeriod instance from given variable if possible. - * - * @param mixed $var - * - * @return static|null */ - public static function make($var) + public static function make(mixed $var): ?static { try { return static::instance($var); - } catch (NotAPeriodException $e) { + } catch (NotAPeriodException) { return static::create($var); } } /** * Create a new instance from a DatePeriod or CarbonPeriod object. - * - * @param CarbonPeriod|DatePeriod $period - * - * @return static */ - public static function instance($period) + public static function instance(mixed $period): static { if ($period instanceof static) { return $period->copy(); @@ -384,9 +366,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable if ($period instanceof self) { return new static( $period->getStartDate(), - $period->getEndDate() ?: $period->getRecurrences(), + $period->getEndDate() ?? $period->getRecurrences(), $period->getDateInterval(), - $period->getOptions() + $period->getOptions(), ); } @@ -395,71 +377,127 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $period->start, $period->end ?: ($period->recurrences - 1), $period->interval, - $period->include_start_date ? 0 : static::EXCLUDE_START_DATE + $period->include_start_date ? 0 : static::EXCLUDE_START_DATE, ); } $class = static::class; $type = \gettype($period); + $chunks = explode('::', __METHOD__); throw new NotAPeriodException( - 'Argument 1 passed to '.$class.'::'.__METHOD__.'() '. + 'Argument 1 passed to '.$class.'::'.end($chunks).'() '. 'must be an instance of DatePeriod or '.$class.', '. - ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.' + ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.', ); } /** * Create a new instance. - * - * @return static */ - public static function create(...$params) + public static function create(...$params): static { return static::createFromArray($params); } /** * Create a new instance from an array of parameters. - * - * @param array $params - * - * @return static */ - public static function createFromArray(array $params) + public static function createFromArray(array $params): static { return new static(...$params); } /** * Create CarbonPeriod from ISO 8601 string. - * - * @param string $iso - * @param int|null $options - * - * @return static */ - public static function createFromIso($iso, $options = null) + public static function createFromIso(string $iso, ?int $options = null): static { $params = static::parseIso8601($iso); $instance = static::createFromArray($params); - if ($options !== null) { - $instance->setOptions($options); - } + $instance->options = ($instance instanceof CarbonPeriodImmutable ? static::IMMUTABLE : 0) | $options; + $instance->handleChangedParameters(); return $instance; } + public static function createFromISO8601String(string $iso, ?int $options = null): static + { + return self::createFromIso($iso, $options); + } + + public static function monthly( + DateTimeInterface|string|int|float|null $start = null, + DateTimeInterface|string|int|float|null $end = null, + ?int $recurrences = null, + ?int $anchorDay = null, + OverflowMode $mode = OverflowMode::AnchorDay, + ?int $options = null, + ): static { + [$start, $end] = self::getStartAndEndForCyclePeriod($start, $end, $recurrences, $anchorDay, $mode); + + return (new static( + $start, + self::getMonthInterval($start, $anchorDay, $mode), + $end ?? $recurrences, + ))->setOptions($options ?? self::IMMUTABLE); + } + + public static function quarterly( + DateTimeInterface|string|int|float|null $start = null, + DateTimeInterface|string|int|float|null $end = null, + ?int $recurrences = null, + ?int $anchorDay = null, + OverflowMode $mode = OverflowMode::AnchorDay, + ?int $options = null, + ): static { + [$start, $end] = self::getStartAndEndForCyclePeriod($start, $end, $recurrences, $anchorDay, $mode); + $interval = self::getMonthInterval($start, $anchorDay, $mode); + + return (new static( + $start, + static function (CarbonInterface $date, bool $negated) use ($interval): CarbonInterface { + for ($i = 0; $i < CarbonInterface::MONTHS_PER_QUARTER; $i++) { + $date = $negated + ? $date->sub($interval) + : $date->add($interval); + } + + return $date; + }, + $end ?? $recurrences, + ))->setOptions($options ?? self::IMMUTABLE); + } + + public static function yearly( + DateTimeInterface|string|int|float|null $start = null, + DateTimeInterface|string|int|float|null $end = null, + ?int $recurrences = null, + ?int $anchorDay = null, + OverflowMode $mode = OverflowMode::AnchorDay, + ?int $options = null, + ): static { + [$start, $end] = self::getStartAndEndForCyclePeriod($start, $end, $recurrences, $anchorDay, $mode); + + return (new static( + $start, + match ($mode) { + OverflowMode::AnchorDay => CarbonInterval::yearWithAnchorDay( + $anchorDay ?? (int) $start->format('d'), + ), + OverflowMode::NoOverflow => CarbonInterval::yearNoOverflow(), + OverflowMode::Overflow => CarbonInterval::year(), + }, + $end ?? $recurrences, + ))->setOptions($options ?? self::IMMUTABLE); + } + /** - * Return whether given interval contains non zero value of any time unit. - * - * @param \DateInterval $interval - * - * @return bool + * Return whether the given interval contains non-zero value of any time unit. */ - protected static function intervalHasTime(DateInterval $interval) + protected static function intervalHasTime(DateInterval $interval): bool { return $interval->h || $interval->i || $interval->s || $interval->f; } @@ -468,13 +506,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Return whether given variable is an ISO 8601 specification. * * Note: Check is very basic, as actual validation will be done later when parsing. - * We just want to ensure that variable is not any other type of a valid parameter. - * - * @param mixed $var - * - * @return bool + * We just want to ensure that variable is not any other type of valid parameter. */ - protected static function isIso8601($var) + protected static function isIso8601(mixed $var): bool { if (!\is_string($var)) { return false; @@ -491,13 +525,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Parse given ISO 8601 string into an array of arguments. * - * @SuppressWarnings(PHPMD.ElseExpression) - * - * @param string $iso - * - * @return array + * @SuppressWarnings(ElseExpression) */ - protected static function parseIso8601($iso) + protected static function parseIso8601(string $iso): array { $result = []; @@ -509,7 +539,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable foreach (explode('/', $iso) as $key => $part) { if ($key === 0 && preg_match('/^R(\d*|INF)$/', $part, $match)) { $parsed = \strlen($match[1]) ? (($match[1] !== 'INF') ? (int) $match[1] : INF) : null; - } elseif ($interval === null && $parsed = CarbonInterval::make($part)) { + } elseif ($interval === null && $parsed = self::makeInterval($part)) { $interval = $part; } elseif ($start === null && $parsed = $dateClass::make($part)) { $start = $part; @@ -527,13 +557,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Add missing parts of the target date from the source date. - * - * @param string $source - * @param string $target - * - * @return string */ - protected static function addMissingParts($source, $target) + protected static function addMissingParts(string $source, string $target): string { $pattern = '/'.preg_replace('/\d+/', '[0-9]+', preg_quote($target, '/')).'$/'; @@ -542,9 +567,81 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return $count ? $result : $target; } + private static function getStartAndEndForCyclePeriod( + DateTimeInterface|string|int|float|null $start = null, + DateTimeInterface|string|int|float|null $end = null, + ?int $recurrences = null, + ?int $anchorDay = null, + OverflowMode $mode = OverflowMode::AnchorDay, + ): array { + if ($anchorDay !== null && $mode !== OverflowMode::AnchorDay) { + throw new InvalidArgumentException( + '$anchorDay parameter must not be set for $mode OverflowMode::'.$mode->name, + ); + } + + if ($end !== null && $recurrences !== null) { + throw new InvalidArgumentException( + 'You must specify $end or $recurrences but not both', + ); + } + + if (\is_int($start) || \is_float($start)) { + $start = CarbonImmutable::createFromTimestamp($start); + } elseif (\is_string($start)) { + $start = CarbonImmutable::parse($start); + } + + $start ??= CarbonImmutable::now(); + + if (\is_int($end) || \is_float($end)) { + $end = CarbonImmutable::createFromTimestamp($end); + } + + return [$start, $end]; + } + + private static function getMonthInterval( + DateTimeInterface|string|int|null $start = null, + ?int $anchorDay = null, + OverflowMode $mode = OverflowMode::AnchorDay, + ): CarbonInterval { + return match ($mode) { + OverflowMode::AnchorDay => CarbonInterval::monthWithAnchorDay( + $anchorDay ?? (int) $start->format('d'), + ), + OverflowMode::NoOverflow => CarbonInterval::monthNoOverflow(), + OverflowMode::Overflow => CarbonInterval::month(), + }; + } + + private static function makeInterval(mixed $input): ?CarbonInterval + { + try { + return CarbonInterval::make($input); + } catch (Throwable) { + return null; + } + } + + private static function makeTimezone(mixed $input): ?CarbonTimeZone + { + if (!\is_string($input)) { + return null; + } + + try { + return CarbonTimeZone::create($input); + } catch (Throwable) { + return null; + } + } + /** * Register a custom macro. * + * Pass null macro to remove it. + * * @example * ``` * CarbonPeriod::macro('middle', function () { @@ -553,12 +650,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle(); * ``` * - * @param string $name - * @param object|callable $macro - * - * @return void + * @param-closure-this static $macro */ - public static function macro($name, $macro) + public static function macro(string $name, ?callable $macro): void { static::$macros[$name] = $macro; } @@ -591,45 +685,30 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3); * ``` * - * @param object|string $mixin - * * @throws ReflectionException - * - * @return void */ - public static function mixin($mixin) + public static function mixin(object|string $mixin): void { static::baseMixin($mixin); } /** * Check if macro is registered. - * - * @param string $name - * - * @return bool */ - public static function hasMacro($name) + public static function hasMacro(string $name): bool { return isset(static::$macros[$name]); } /** * Provide static proxy for instance aliases. - * - * @param string $method - * @param array $parameters - * - * @return mixed */ - public static function __callStatic($method, $parameters) + public static function __callStatic(string $method, array $parameters): mixed { $date = new static(); if (static::hasMacro($method)) { - return static::bindMacroContext(null, function () use (&$method, &$parameters, &$date) { - return $date->callMacro($method, $parameters); - }); + return static::bindMacroContext(null, static fn () => $date->callMacro($method, $parameters)); } return $date->$method(...$parameters); @@ -638,14 +717,23 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * CarbonPeriod constructor. * - * @SuppressWarnings(PHPMD.ElseExpression) + * @SuppressWarnings(ElseExpression) * * @throws InvalidArgumentException */ public function __construct(...$arguments) { - if (is_a($this->dateClass, DateTimeImmutable::class, true)) { - $this->options = static::IMMUTABLE; + $raw = null; + + if (isset($arguments['raw'])) { + $raw = $arguments['raw']; + $this->isDefaultInterval = $arguments['isDefaultInterval'] ?? false; + + if (isset($arguments['dateClass'])) { + $this->dateClass = $arguments['dateClass']; + } + + $arguments = $raw; } // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, @@ -658,56 +746,74 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable } if ($argumentsCount === 1) { - if ($arguments[0] instanceof DatePeriod) { + if ($arguments[0] instanceof self) { + $arguments = [ + $arguments[0]->getStartDate(), + $arguments[0]->getEndDate() ?? $arguments[0]->getRecurrences(), + $arguments[0]->getDateInterval(), + $arguments[0]->getOptions(), + ]; + } elseif ($arguments[0] instanceof DatePeriod) { $arguments = [ $arguments[0]->start, $arguments[0]->end ?: ($arguments[0]->recurrences - 1), $arguments[0]->interval, $arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE, ]; - } elseif ($arguments[0] instanceof self) { - $arguments = [ - $arguments[0]->getStartDate(), - $arguments[0]->getEndDate() ?: $arguments[0]->getRecurrences(), - $arguments[0]->getDateInterval(), - $arguments[0]->getOptions(), - ]; } } + if (is_a($this->dateClass, DateTimeImmutable::class, true)) { + $this->options = static::IMMUTABLE; + } + $optionsSet = false; + $originalArguments = []; + $sortedArguments = []; foreach ($arguments as $argument) { $parsedDate = null; if ($argument instanceof DateTimeZone) { - $this->setTimezone($argument); - } elseif ($this->dateInterval === null && + $sortedArguments = $this->configureTimezone($argument, $sortedArguments, $originalArguments); + } elseif (!isset($sortedArguments['interval']) && ( (\is_string($argument) && preg_match( '/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T\d].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', - $argument + $argument, )) || $argument instanceof DateInterval || - $argument instanceof Closure + $argument instanceof Duration || + $argument instanceof Closure || + $argument instanceof Unit ) && - $parsedInterval = @CarbonInterval::make($argument) + $parsedInterval = self::makeInterval($argument) ) { - $this->setDateInterval($parsedInterval); - } elseif ($this->startDate === null && $parsedDate = $this->makeDateTime($argument)) { - $this->setStartDate($parsedDate); - } elseif ($this->endDate === null && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) { - $this->setEndDate($parsedDate); - } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) { - $this->setRecurrences($argument); + $sortedArguments['interval'] = $parsedInterval; + } elseif (!isset($sortedArguments['start']) && $parsedDate = $this->makeDateTime($argument)) { + $sortedArguments['start'] = $parsedDate; + $originalArguments['start'] = $argument; + } elseif (!isset($sortedArguments['end']) && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) { + $sortedArguments['end'] = $parsedDate; + $originalArguments['end'] = $argument; + } elseif (!isset($sortedArguments['recurrences']) && + !isset($sortedArguments['end']) && + (\is_int($argument) || \is_float($argument)) + && $argument >= 0 + ) { + $sortedArguments['recurrences'] = $argument; } elseif (!$optionsSet && (\is_int($argument) || $argument === null)) { $optionsSet = true; - $this->setOptions(((int) $this->options) | ((int) $argument)); + $sortedArguments['options'] = (((int) $this->options) | ((int) $argument)); + } elseif ($parsedTimezone = self::makeTimezone($argument)) { + $sortedArguments = $this->configureTimezone($parsedTimezone, $sortedArguments, $originalArguments); } else { throw new InvalidPeriodParameterException('Invalid constructor parameters.'); } } + $this->setFromAssociativeArray($sortedArguments); + if ($this->startDate === null) { $dateClass = $this->dateClass; $this->setStartDate($dateClass::now()); @@ -723,15 +829,20 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $this->setOptions(0); } + parent::__construct( + $this->startDate, + $this->dateInterval, + $this->endDate ?? max(1, min(2147483639, $this->recurrences ?? 1)), + $this->options, + ); + $this->constructed = true; } /** * Get a copy of the instance. - * - * @return static */ - public function copy() + public function copy(): static { return clone $this; } @@ -739,44 +850,34 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Prepare the instance to be set (self if mutable to be mutated, * copy if immutable to generate a new instance). - * - * @return static */ - protected function copyIfImmutable() + protected function copyIfImmutable(): static { return $this; } /** * Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names. - * - * @param string $name - * - * @return callable|null */ - protected function getGetter(string $name) + protected function getGetter(string $name): ?callable { - switch (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) { - case 'start': - case 'start_date': - return [$this, 'getStartDate']; - case 'end': - case 'end_date': - return [$this, 'getEndDate']; - case 'interval': - case 'date_interval': - return [$this, 'getDateInterval']; - case 'recurrences': - return [$this, 'getRecurrences']; - case 'include_start_date': - return [$this, 'isStartIncluded']; - case 'include_end_date': - return [$this, 'isEndIncluded']; - case 'current': - return [$this, 'current']; - default: - return null; - } + return match (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) { + 'start', 'start_date' => [$this, 'getStartDate'], + 'end', 'end_date' => [$this, 'getEndDate'], + 'interval', 'date_interval' => [$this, 'getDateInterval'], + 'recurrences' => [$this, 'getRecurrences'], + 'include_start_date' => [$this, 'isStartIncluded'], + 'include_end_date' => [$this, 'isEndIncluded'], + 'current' => [$this, 'current'], + 'locale' => [$this, 'locale'], + 'tzname', 'tz_name' => fn () => match (true) { + $this->timezoneSetting === null => null, + \is_string($this->timezoneSetting) => $this->timezoneSetting, + $this->timezoneSetting instanceof DateTimeZone => $this->timezoneSetting->getName(), + default => CarbonTimeZone::instance($this->timezoneSetting)->getName(), + }, + default => null, + }; } /** @@ -842,9 +943,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable */ public function setDateClass(string $dateClass) { - if (!is_a($dateClass, CarbonInterface::class, true)) { - throw new NotACarbonClassException($dateClass); - } + NotACarbonClassException::expectCarbonInterface($dateClass); $self = $this->copyIfImmutable(); $self->dateClass = $dateClass; @@ -871,15 +970,24 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Change the period date interval. * - * @param DateInterval|string $interval + * @param DateInterval|Duration|Unit|string|int $interval + * @param Unit|string $unit the unit of $interval if it's a number * * @throws InvalidIntervalException * * @return static */ - public function setDateInterval($interval) + public function setDateInterval(mixed $interval, Unit|string|null $unit = null): static { - if (!$interval = CarbonInterval::make($interval)) { + if ($interval instanceof Unit) { + $interval = $interval->interval(); + } + + if ($unit instanceof Unit) { + $unit = $unit->name; + } + + if (!$interval = self::makeCarbonInterval($interval, $unit)) { throw new InvalidIntervalException('Invalid interval.'); } @@ -898,11 +1006,25 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable } /** - * Invert the period date interval. + * Reset the date interval to the default value. * - * @return static + * Difference with simply setting interval to 1-day is that P1D will not appear when calling toIso8601String() + * and also next adding to the interval won't include the default 1-day. */ - public function invertDateInterval() + public function resetDateInterval(): static + { + $self = $this->copyIfImmutable(); + $self->setDateInterval(CarbonInterval::day()); + + $self->isDefaultInterval = true; + + return $self; + } + + /** + * Invert the period date interval. + */ + public function invertDateInterval(): static { return $this->setDateInterval($this->dateInterval->invert()); } @@ -915,7 +1037,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return static */ - public function setDates($start, $end) + public function setDates(mixed $start, mixed $end): static { return $this->setStartDate($start)->setEndDate($end); } @@ -925,18 +1047,12 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @param int|null $options * - * @throws InvalidArgumentException - * * @return static */ - public function setOptions($options) + public function setOptions(?int $options): static { - if (!\is_int($options) && $options !== null) { - throw new InvalidPeriodParameterException('Invalid options.'); - } - $self = $this->copyIfImmutable(); - $self->options = $options ?: 0; + $self->options = $options ?? 0; $self->handleChangedParameters(); @@ -945,12 +1061,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Get the period options. - * - * @return int */ - public function getOptions() + public function getOptions(): int { - return $this->options; + return $this->options ?? 0; } /** @@ -959,53 +1073,45 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * @param int $options * @param bool|null $state * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException * * @return static */ - public function toggleOptions($options, $state = null) + public function toggleOptions(int $options, ?bool $state = null): static { + $self = $this->copyIfImmutable(); + if ($state === null) { $state = ($this->options & $options) !== $options; } - return $this->setOptions( + return $self->setOptions( $state ? $this->options | $options : - $this->options & ~$options + $this->options & ~$options, ); } /** * Toggle EXCLUDE_START_DATE option. - * - * @param bool $state - * - * @return static */ - public function excludeStartDate($state = true) + public function excludeStartDate(bool $state = true): static { return $this->toggleOptions(static::EXCLUDE_START_DATE, $state); } /** * Toggle EXCLUDE_END_DATE option. - * - * @param bool $state - * - * @return static */ - public function excludeEndDate($state = true) + public function excludeEndDate(bool $state = true): static { return $this->toggleOptions(static::EXCLUDE_END_DATE, $state); } /** * Get the underlying date interval. - * - * @return CarbonInterval */ - public function getDateInterval() + public function getDateInterval(): CarbonInterval { return $this->dateInterval->copy(); } @@ -1014,10 +1120,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Get start date of the period. * * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. - * - * @return CarbonInterface */ - public function getStartDate(string $rounding = null) + public function getStartDate(?string $rounding = null): CarbonInterface { $date = $this->startDate->avoidMutation(); @@ -1028,10 +1132,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Get end date of the period. * * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. - * - * @return CarbonInterface|null */ - public function getEndDate(string $rounding = null) + public function getEndDate(?string $rounding = null): ?CarbonInterface { if (!$this->endDate) { return null; @@ -1044,60 +1146,49 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Get number of recurrences. - * - * @return int|float|null */ - public function getRecurrences() + #[ReturnTypeWillChange] + public function getRecurrences(): int|float|null { - return $this->recurrences; + return $this->carbonRecurrences; } /** * Returns true if the start date should be excluded. - * - * @return bool */ - public function isStartExcluded() + public function isStartExcluded(): bool { return ($this->options & static::EXCLUDE_START_DATE) !== 0; } /** * Returns true if the end date should be excluded. - * - * @return bool */ - public function isEndExcluded() + public function isEndExcluded(): bool { return ($this->options & static::EXCLUDE_END_DATE) !== 0; } /** * Returns true if the start date should be included. - * - * @return bool */ - public function isStartIncluded() + public function isStartIncluded(): bool { return !$this->isStartExcluded(); } /** * Returns true if the end date should be included. - * - * @return bool */ - public function isEndIncluded() + public function isEndIncluded(): bool { return !$this->isEndExcluded(); } /** * Return the start if it's included by option, else return the start + 1 period interval. - * - * @return CarbonInterface */ - public function getIncludedStartDate() + public function getIncludedStartDate(): CarbonInterface { $start = $this->getStartDate(); @@ -1111,10 +1202,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return the end if it's included by option, else return the end - 1 period interval. * Warning: if the period has no fixed end, this method will iterate the period to calculate it. - * - * @return CarbonInterface */ - public function getIncludedEndDate() + public function getIncludedEndDate(): CarbonInterface { $end = $this->getEndDate(); @@ -1132,14 +1221,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Add a filter to the stack. * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @param callable $callback - * @param string $name - * - * @return static + * @SuppressWarnings(UnusedFormalParameter) */ - public function addFilter($callback, $name = null) + public function addFilter(callable|string $callback, ?string $name = null): static { $self = $this->copyIfImmutable(); $tuple = $self->createFilterTuple(\func_get_args()); @@ -1154,14 +1238,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Prepend a filter to the stack. * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @param callable $callback - * @param string $name - * - * @return static + * @SuppressWarnings(UnusedFormalParameter) */ - public function prependFilter($callback, $name = null) + public function prependFilter(callable|string $callback, ?string $name = null): static { $self = $this->copyIfImmutable(); $tuple = $self->createFilterTuple(\func_get_args()); @@ -1175,21 +1254,15 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Remove a filter by instance or name. - * - * @param callable|string $filter - * - * @return static */ - public function removeFilter($filter) + public function removeFilter(callable|string $filter): static { $self = $this->copyIfImmutable(); $key = \is_callable($filter) ? 0 : 1; $self->filters = array_values(array_filter( $this->filters, - function ($tuple) use ($key, $filter) { - return $tuple[$key] !== $filter; - } + static fn ($tuple) => $tuple[$key] !== $filter, )); $self->updateInternalState(); @@ -1201,12 +1274,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return whether given instance or name is in the filter stack. - * - * @param callable|string $filter - * - * @return bool */ - public function hasFilter($filter) + public function hasFilter(callable|string $filter): bool { $key = \is_callable($filter) ? 0 : 1; @@ -1221,22 +1290,16 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Get filters stack. - * - * @return array */ - public function getFilters() + public function getFilters(): array { return $this->filters; } /** * Set filters stack. - * - * @param array $filters - * - * @return static */ - public function setFilters(array $filters) + public function setFilters(array $filters): static { $self = $this->copyIfImmutable(); $self->filters = $filters; @@ -1250,10 +1313,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Reset filters stack. - * - * @return static */ - public function resetFilters() + public function resetFilters(): static { $self = $this->copyIfImmutable(); $self->filters = []; @@ -1262,7 +1323,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $self->filters[] = [static::END_DATE_FILTER, null]; } - if ($self->recurrences !== null) { + if ($self->carbonRecurrences !== null) { $self->filters[] = [static::RECURRENCES_FILTER, null]; } @@ -1274,33 +1335,23 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Add a recurrences filter (set maximum number of recurrences). * - * @param int|float|null $recurrences - * * @throws InvalidArgumentException - * - * @return static */ - public function setRecurrences($recurrences) + public function setRecurrences(int|float|null $recurrences): static { - if ((!is_numeric($recurrences) && $recurrences !== null) || $recurrences < 0) { - throw new InvalidPeriodParameterException('Invalid number of recurrences.'); - } - if ($recurrences === null) { return $this->removeFilter(static::RECURRENCES_FILTER); } - /** @var self $self */ - $self = $this->copyIfImmutable(); - $self->recurrences = $recurrences === INF ? INF : (int) $recurrences; - - if (!$self->hasFilter(static::RECURRENCES_FILTER)) { - return $self->addFilter(static::RECURRENCES_FILTER); + if ($recurrences < 0) { + throw new InvalidPeriodParameterException('Invalid number of recurrences.'); } - $self->handleChangedParameters(); + /** @var self $self */ + $self = $this->copyIfImmutable(); + $self->carbonRecurrences = $recurrences === INF ? INF : (int) $recurrences; - return $self; + return self::addFilterOrHandleChangedParameters($self, static::RECURRENCES_FILTER); } /** @@ -1313,9 +1364,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return static */ - public function setStartDate($date, $inclusive = null) + public function setStartDate(mixed $date, ?bool $inclusive = null): static { - if (!$this->isInfiniteDate($date) && !($date = ([$this->dateClass, 'make'])($date))) { + if (!$this->isInfiniteDate($date) && !($date = ([$this->dateClass, 'make'])($date, $this->timezone))) { throw new InvalidPeriodDateException('Invalid start date.'); } @@ -1326,6 +1377,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive); } + $self->syncNativePeriod(); + return $self; } @@ -1339,50 +1392,60 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return static */ - public function setEndDate($date, $inclusive = null) + public function setEndDate(mixed $date, ?bool $inclusive = null): static { - if ($date !== null && !$this->isInfiniteDate($date) && !$date = ([$this->dateClass, 'make'])($date)) { + if ($date !== null && !$this->isInfiniteDate($date) && !$date = ([$this->dateClass, 'make'])($date, $this->timezone)) { throw new InvalidPeriodDateException('Invalid end date.'); } - if (!$date) { - return $this->removeFilter(static::END_DATE_FILTER); - } + // ::make() is responsible for converting strings to DateTimeInterface objects + \assert(!\is_string($date)); $self = $this->copyIfImmutable(); + + if (!$date) { + $self = $self->removeFilter(static::END_DATE_FILTER); + $self->syncNativePeriod(); + + return $self; + } + + \assert($date instanceof DateTimeInterface); + $self->endDate = $date; + if ( + $self->startDate !== null + && $self->dateInterval !== null + && !$self->dateInterval->invert + && $self->startDate > $self->endDate + ) { + $self->dateInterval->invert = 1; + } + if ($inclusive !== null) { $self = $self->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive); } - if (!$self->hasFilter(static::END_DATE_FILTER)) { - return $self->addFilter(static::END_DATE_FILTER); - } + $self = self::addFilterOrHandleChangedParameters($self, static::END_DATE_FILTER); - $self->handleChangedParameters(); + $self->syncNativePeriod(); return $self; } /** * Check if the current position is valid. - * - * @return bool */ - #[ReturnTypeWillChange] - public function valid() + public function valid(): bool { return $this->validateCurrentDate() === true; } /** * Return the current key. - * - * @return int|null */ - #[ReturnTypeWillChange] - public function key() + public function key(): ?int { return $this->valid() ? $this->key @@ -1391,14 +1454,11 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return the current date. - * - * @return CarbonInterface|null */ - #[ReturnTypeWillChange] - public function current() + public function current(): ?CarbonInterface { return $this->valid() - ? $this->prepareForReturn($this->current) + ? $this->prepareForReturn($this->carbonCurrent) : null; } @@ -1406,13 +1466,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Move forward to the next date. * * @throws RuntimeException - * - * @return void */ - #[ReturnTypeWillChange] - public function next() + public function next(): void { - if ($this->current === null) { + if ($this->carbonCurrent === null) { $this->rewind(); } @@ -1433,25 +1490,22 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time * * @throws RuntimeException - * - * @return void */ - #[ReturnTypeWillChange] - public function rewind() + public function rewind(): void { $this->key = 0; - $this->current = ([$this->dateClass, 'make'])($this->startDate); + $this->carbonCurrent = ([$this->dateClass, 'make'])($this->startDate); $settings = $this->getSettings(); if ($this->hasLocalTranslator()) { $settings['locale'] = $this->getTranslatorLocale(); } - $this->current->settings($settings); - $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null; + $this->carbonCurrent->settings($settings); + $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->carbonCurrent->getTimezone() : null; if ($this->timezone) { - $this->current = $this->current->utc(); + $this->carbonCurrent = $this->carbonCurrent->utc(); } $this->validationResult = null; @@ -1468,7 +1522,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return bool */ - public function skip($count = 1) + public function skip(int $count = 1): bool { for ($i = $count; $this->valid() && $i > 0; $i--) { $this->next(); @@ -1479,20 +1533,20 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Format the date period as ISO 8601. - * - * @return string */ - public function toIso8601String() + public function toIso8601String(): string { $parts = []; - if ($this->recurrences !== null) { - $parts[] = 'R'.$this->recurrences; + if ($this->carbonRecurrences !== null) { + $parts[] = 'R'.$this->carbonRecurrences; } $parts[] = $this->startDate->toIso8601String(); - $parts[] = $this->dateInterval->spec(); + if (!$this->isDefaultInterval) { + $parts[] = $this->dateInterval->spec(); + } if ($this->endDate !== null) { $parts[] = $this->endDate->toIso8601String(); @@ -1503,12 +1557,12 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Convert the date period into a string. - * - * @return string */ - public function toString() + public function toString(): string { - $format = $this->localToStringFormat ?? static::$toStringFormat; + $format = $this->localToStringFormat + ?? $this->getFactory()->getSettings()['toStringFormat'] + ?? null; if ($format instanceof Closure) { return $format($this); @@ -1524,8 +1578,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable : 'Y-m-d' ); - if ($this->recurrences !== null) { - $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator); + if ($this->carbonRecurrences !== null) { + $parts[] = $this->translate('period_recurrences', [], $this->carbonRecurrences, $translator); } $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([ @@ -1545,10 +1599,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Format the date period as ISO 8601. - * - * @return string */ - public function spec() + public function spec(): string { return $this->toIso8601String(); } @@ -1558,9 +1610,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @param string $className The $className::instance() method will be called to cast the current object. * - * @return DatePeriod + * @return DatePeriod|object */ - public function cast(string $className) + public function cast(string $className): object { if (!method_exists($className, 'instance')) { if (is_a($className, DatePeriod::class, true)) { @@ -1568,7 +1620,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $this->rawDate($this->getStartDate()), $this->getDateInterval(), $this->getEndDate() ? $this->rawDate($this->getIncludedEndDate()) : $this->getRecurrences(), - $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0 + $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0, ); } @@ -1585,10 +1637,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * ``` * var_dump(CarbonPeriod::create('2021-01-05', '2021-02-15')->toDatePeriod()); * ``` - * - * @return DatePeriod */ - public function toDatePeriod() + public function toDatePeriod(): DatePeriod { return $this->cast(DatePeriod::class); } @@ -1605,7 +1655,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable foreach ($this->filters as $filter) { switch ($filter) { case [static::RECURRENCES_FILTER, null]: - if ($this->recurrences !== null && is_finite($this->recurrences)) { + if ($this->carbonRecurrences !== null && is_finite($this->carbonRecurrences)) { return false; } @@ -1631,7 +1681,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return CarbonInterface[] */ - public function toArray() + public function toArray(): array { if ($this->isUnfilteredAndEndLess()) { throw new EndLessPeriodException("Endless period can't be converted to array nor counted."); @@ -1639,34 +1689,29 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $state = [ $this->key, - $this->current ? $this->current->avoidMutation() : null, + $this->carbonCurrent ? $this->carbonCurrent->avoidMutation() : null, $this->validationResult, ]; $result = iterator_to_array($this); - [$this->key, $this->current, $this->validationResult] = $state; + [$this->key, $this->carbonCurrent, $this->validationResult] = $state; return $result; } /** * Count dates in the date period. - * - * @return int */ - #[ReturnTypeWillChange] - public function count() + public function count(): int { return \count($this->toArray()); } /** * Return the first date in the date period. - * - * @return CarbonInterface|null */ - public function first() + public function first(): ?CarbonInterface { if ($this->isUnfilteredAndEndLess()) { foreach ($this as $date) { @@ -1683,10 +1728,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return the last date in the date period. - * - * @return CarbonInterface|null */ - public function last() + public function last(): ?CarbonInterface { $array = $this->toArray(); @@ -1695,10 +1738,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Convert the date period into a string. - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->toString(); } @@ -1712,18 +1753,11 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * ->count() * * Note: We use magic method to let static and instance aliases with the same names. - * - * @param string $method - * @param array $parameters - * - * @return mixed */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): mixed { if (static::hasMacro($method)) { - return static::bindMacroContext($this, function () use (&$method, &$parameters) { - return $this->callMacro($method, $parameters); - }); + return static::bindMacroContext($this, fn () => $this->callMacro($method, $parameters)); } $roundedValue = $this->callRoundMethod($method, $parameters); @@ -1732,9 +1766,15 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return $roundedValue; } + $count = \count($parameters); + switch ($method) { case 'start': case 'since': + if ($count === 0) { + return $this->getStartDate(); + } + self::setDefaultParameters($parameters, [ [0, 'date', null], ]); @@ -1746,6 +1786,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable case 'end': case 'until': + if ($count === 0) { + return $this->getEndDate(); + } + self::setDefaultParameters($parameters, [ [0, 'date', null], ]); @@ -1766,6 +1810,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable case 'recurrences': case 'times': + if ($count === 0) { + return $this->getRecurrences(); + } + self::setDefaultParameters($parameters, [ [0, 'recurrences', null], ]); @@ -1773,6 +1821,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return $this->setRecurrences(...$parameters); case 'options': + if ($count === 0) { + return $this->getOptions(); + } + self::setDefaultParameters($parameters, [ [0, 'options', null], ]); @@ -1794,6 +1846,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return $this->prependFilter(...$parameters); case 'filters': + if ($count === 0) { + return $this->getFilters(); + } + self::setDefaultParameters($parameters, [ [0, 'filters', []], ]); @@ -1805,6 +1861,10 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable case 'every': case 'step': case 'stepBy': + if ($count === 0) { + return $this->getDateInterval(); + } + return $this->setDateInterval(...$parameters); case 'invert': @@ -1846,16 +1906,12 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Set the instance's timezone from a string or object and apply it to start/end. - * - * @param \DateTimeZone|string $timezone - * - * @return static */ - public function setTimezone($timezone) + public function setTimezone(DateTimeZone|string|int $timezone): static { $self = $this->copyIfImmutable(); - $self->tzName = $timezone; - $self->timezone = $timezone; + $self->timezoneSetting = $timezone; + $self->timezone = CarbonTimeZone::instance($timezone); if ($self->startDate) { $self = $self->setStartDate($self->startDate->setTimezone($timezone)); @@ -1870,16 +1926,12 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Set the instance's timezone from a string or object and add/subtract the offset difference to start/end. - * - * @param \DateTimeZone|string $timezone - * - * @return static */ - public function shiftTimezone($timezone) + public function shiftTimezone(DateTimeZone|string|int $timezone): static { $self = $this->copyIfImmutable(); - $self->tzName = $timezone; - $self->timezone = $timezone; + $self->timezoneSetting = $timezone; + $self->timezone = CarbonTimeZone::instance($timezone); if ($self->startDate) { $self = $self->setStartDate($self->startDate->shiftTimezone($timezone)); @@ -1893,13 +1945,13 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable } /** - * Returns the end is set, else calculated from start an recurrences. + * Returns the end is set, else calculated from start and recurrences. * * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval. * * @return CarbonInterface */ - public function calculateEnd(string $rounding = null) + public function calculateEnd(?string $rounding = null): CarbonInterface { if ($end = $this->getEndDate($rounding)) { return $end; @@ -1918,19 +1970,16 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return $date; } - /** - * @return CarbonInterface|null - */ - private function getEndFromRecurrences() + private function getEndFromRecurrences(): ?CarbonInterface { - if ($this->recurrences === null) { + if ($this->carbonRecurrences === null) { throw new UnreachableException( "Could not calculate period end without either explicit end or recurrences.\n". - "If you're looking for a forever-period, use ->setRecurrences(INF)." + "If you're looking for a forever-period, use ->setRecurrences(INF).", ); } - if ($this->recurrences === INF) { + if ($this->carbonRecurrences === INF) { $start = $this->getStartDate(); return $start < $start->avoidMutation()->add($this->getDateInterval()) @@ -1941,18 +1990,15 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable if ($this->filters === [[static::RECURRENCES_FILTER, null]]) { return $this->getStartDate()->avoidMutation()->add( $this->getDateInterval()->times( - $this->recurrences - ($this->isStartExcluded() ? 0 : 1) - ) + $this->carbonRecurrences - ($this->isStartExcluded() ? 0 : 1), + ), ); } return null; } - /** - * @return CarbonInterface|null - */ - private function iterateUntilEnd() + private function iterateUntilEnd(): ?CarbonInterface { $attempts = 0; $date = null; @@ -1960,7 +2006,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable foreach ($this as $date) { if (++$attempts > static::END_MAX_ATTEMPTS) { throw new UnreachableException( - 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.' + 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.', ); } } @@ -1977,7 +2023,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return bool */ - public function overlaps($rangeOrRangeStart, $rangeEnd = null) + public function overlaps(mixed $rangeOrRangeStart, mixed $rangeEnd = null): bool { $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart; @@ -2000,10 +2046,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * echo $date->diffInDays('2020-12-25')." days before Christmas!\n"; * }); * ``` - * - * @param callable $callback */ - public function forEach(callable $callback) + public function forEach(callable $callback): void { foreach ($this as $date) { $callback($date); @@ -2020,12 +2064,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * return $date->diffInDays('2020-12-25').' days before Christmas!'; * }))); * ``` - * - * @param callable $callback - * - * @return \Generator */ - public function map(callable $callback) + public function map(callable $callback): Generator { foreach ($this as $date) { yield $callback($date); @@ -2036,13 +2076,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Determines if the instance is equal to another. * Warning: if options differ, instances will never be equal. * - * @param mixed $period - * * @see equalTo() - * - * @return bool */ - public function eq($period): bool + public function eq(mixed $period): bool { return $this->equalTo($period); } @@ -2050,12 +2086,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the instance is equal to another. * Warning: if options differ, instances will never be equal. - * - * @param mixed $period - * - * @return bool */ - public function equalTo($period): bool + public function equalTo(mixed $period): bool { if (!($period instanceof self)) { $period = self::make($period); @@ -2074,13 +2106,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Determines if the instance is not equal to another. * Warning: if options differ, instances will never be equal. * - * @param mixed $period - * * @see notEqualTo() - * - * @return bool */ - public function ne($period): bool + public function ne(mixed $period): bool { return $this->notEqualTo($period); } @@ -2088,25 +2116,17 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the instance is not equal to another. * Warning: if options differ, instances will never be equal. - * - * @param mixed $period - * - * @return bool */ - public function notEqualTo($period): bool + public function notEqualTo(mixed $period): bool { return !$this->eq($period); } /** - * Determines if the start date is before an other given date. + * Determines if the start date is before another given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function startsBefore($date = null): bool + public function startsBefore(mixed $date = null): bool { return $this->getStartDate()->lessThan($this->resolveCarbon($date)); } @@ -2114,25 +2134,17 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the start date is before or the same as a given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function startsBeforeOrAt($date = null): bool + public function startsBeforeOrAt(mixed $date = null): bool { return $this->getStartDate()->lessThanOrEqualTo($this->resolveCarbon($date)); } /** - * Determines if the start date is after an other given date. + * Determines if the start date is after another given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function startsAfter($date = null): bool + public function startsAfter(mixed $date = null): bool { return $this->getStartDate()->greaterThan($this->resolveCarbon($date)); } @@ -2140,12 +2152,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the start date is after or the same as a given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function startsAfterOrAt($date = null): bool + public function startsAfterOrAt(mixed $date = null): bool { return $this->getStartDate()->greaterThanOrEqualTo($this->resolveCarbon($date)); } @@ -2153,25 +2161,17 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the start date is the same as a given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function startsAt($date = null): bool + public function startsAt(mixed $date = null): bool { return $this->getStartDate()->equalTo($this->resolveCarbon($date)); } /** - * Determines if the end date is before an other given date. + * Determines if the end date is before another given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function endsBefore($date = null): bool + public function endsBefore(mixed $date = null): bool { return $this->calculateEnd()->lessThan($this->resolveCarbon($date)); } @@ -2179,25 +2179,17 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the end date is before or the same as a given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function endsBeforeOrAt($date = null): bool + public function endsBeforeOrAt(mixed $date = null): bool { return $this->calculateEnd()->lessThanOrEqualTo($this->resolveCarbon($date)); } /** - * Determines if the end date is after an other given date. + * Determines if the end date is after another given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function endsAfter($date = null): bool + public function endsAfter(mixed $date = null): bool { return $this->calculateEnd()->greaterThan($this->resolveCarbon($date)); } @@ -2205,12 +2197,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the end date is after or the same as a given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function endsAfterOrAt($date = null): bool + public function endsAfterOrAt(mixed $date = null): bool { return $this->calculateEnd()->greaterThanOrEqualTo($this->resolveCarbon($date)); } @@ -2218,12 +2206,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Determines if the end date is the same as a given date. * (Rather start/end are included by options is ignored.) - * - * @param mixed $date - * - * @return bool */ - public function endsAt($date = null): bool + public function endsAt(mixed $date = null): bool { return $this->calculateEnd()->equalTo($this->resolveCarbon($date)); } @@ -2231,8 +2215,6 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return true if start date is now or later. * (Rather start/end are included by options is ignored.) - * - * @return bool */ public function isStarted(): bool { @@ -2242,8 +2224,6 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return true if end date is now or later. * (Rather start/end are included by options is ignored.) - * - * @return bool */ public function isEnded(): bool { @@ -2253,8 +2233,6 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Return true if now is between start date (included) and end date (excluded). * (Rather start/end are included by options is ignored.) - * - * @return bool */ public function isInProgress(): bool { @@ -2263,15 +2241,12 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Round the current instance at the given unit with given precision if specified and the given function. - * - * @param string $unit - * @param float|int|string|\DateInterval|null $precision - * @param string $function - * - * @return static */ - public function roundUnit($unit, $precision = 1, $function = 'round') - { + public function roundUnit( + string $unit, + DateInterval|float|int|string|null $precision = 1, + callable|string $function = 'round', + ): static { $self = $this->copyIfImmutable(); $self = $self->setStartDate($self->getStartDate()->roundUnit($unit, $precision, $function)); @@ -2284,40 +2259,27 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Truncate the current instance at the given unit with given precision if specified. - * - * @param string $unit - * @param float|int|string|\DateInterval|null $precision - * - * @return static */ - public function floorUnit($unit, $precision = 1) + public function floorUnit(string $unit, DateInterval|float|int|string|null $precision = 1): static { return $this->roundUnit($unit, $precision, 'floor'); } /** * Ceil the current instance at the given unit with given precision if specified. - * - * @param string $unit - * @param float|int|string|\DateInterval|null $precision - * - * @return static */ - public function ceilUnit($unit, $precision = 1) + public function ceilUnit(string $unit, DateInterval|float|int|string|null $precision = 1): static { return $this->roundUnit($unit, $precision, 'ceil'); } /** * Round the current instance second with given precision if specified (else period interval is used). - * - * @param float|int|string|\DateInterval|null $precision - * @param string $function - * - * @return static */ - public function round($precision = null, $function = 'round') - { + public function round( + DateInterval|float|int|string|null $precision = null, + callable|string $function = 'round', + ): static { return $this->roundWith( $precision ?? $this->getDateInterval()->setLocalTranslator(TranslatorImmutable::get('en'))->forHumans(), $function @@ -2326,24 +2288,16 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Round the current instance second with given precision if specified (else period interval is used). - * - * @param float|int|string|\DateInterval|null $precision - * - * @return static */ - public function floor($precision = null) + public function floor(DateInterval|float|int|string|null $precision = null): static { return $this->round($precision, 'floor'); } /** * Ceil the current instance second with given precision if specified (else period interval is used). - * - * @param float|int|string|\DateInterval|null $precision - * - * @return static */ - public function ceil($precision = null) + public function ceil(DateInterval|float|int|string|null $precision = null): static { return $this->round($precision, 'ceil'); } @@ -2355,20 +2309,15 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @return CarbonInterface[] */ - #[ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { return $this->toArray(); } /** * Return true if the given date is between start and end. - * - * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date - * - * @return bool */ - public function contains($date = null): bool + public function contains(mixed $date = null): bool { $startMethod = 'startsBefore'.($this->isStartIncluded() ? 'OrAt' : ''); $endMethod = 'endsAfter'.($this->isEndIncluded() ? 'OrAt' : ''); @@ -2380,12 +2329,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Return true if the current period follows a given other period (with no overlap). * For instance, [2019-08-01 -> 2019-08-12] follows [2019-07-29 -> 2019-07-31] * Note than in this example, follows() would be false if 2019-08-01 or 2019-07-31 was excluded by options. - * - * @param \Carbon\CarbonPeriod|\DatePeriod|string $period - * - * @return bool */ - public function follows($period, ...$arguments): bool + public function follows(mixed $period, mixed ...$arguments): bool { $period = $this->resolveCarbonPeriod($period, ...$arguments); @@ -2396,12 +2341,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Return true if the given other period follows the current one (with no overlap). * For instance, [2019-07-29 -> 2019-07-31] is followed by [2019-08-01 -> 2019-08-12] * Note than in this example, isFollowedBy() would be false if 2019-08-01 or 2019-07-31 was excluded by options. - * - * @param \Carbon\CarbonPeriod|\DatePeriod|string $period - * - * @return bool */ - public function isFollowedBy($period, ...$arguments): bool + public function isFollowedBy(mixed $period, mixed ...$arguments): bool { $period = $this->resolveCarbonPeriod($period, ...$arguments); @@ -2413,29 +2354,150 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * * @see follows() * @see isFollowedBy() - * - * @param \Carbon\CarbonPeriod|\DatePeriod|string $period - * - * @return bool */ - public function isConsecutiveWith($period, ...$arguments): bool + public function isConsecutiveWith(mixed $period, mixed ...$arguments): bool { return $this->follows($period, ...$arguments) || $this->isFollowedBy($period, ...$arguments); } + public function __debugInfo(): array + { + $info = $this->baseDebugInfo(); + unset( + $info['start'], + $info['end'], + $info['interval'], + $info['include_start_date'], + $info['include_end_date'], + $info['constructed'], + $info["\0*\0constructed"], + ); + + return $info; + } + + public function __unserialize(array $data): void + { + try { + $values = array_combine( + array_map( + static fn (string $key): string => preg_replace('/^\0\*\0/', '', $key), + array_keys($data), + ), + $data, + ); + + if (isset($values['dateClass'])) { + NotACarbonClassException::expectCarbonInterface($values['dateClass']); + } + + if ( + isset($values['filters']) + // Non-array filters are not an issue as they will be rejected before being read with: + // Cannot assign X to property Carbon\CarbonPeriod::$filters of type array + && \is_array($values['filters']) + && PeriodFilterSafetyException::isDetectionEnabled() + ) { + foreach ($values['filters'] as $tuple) { + if (\is_array($tuple[0])) { + $subject = $tuple[0][0]; + + if ( + is_a($subject, DatePeriod::class, true) + || is_a($subject, DateInterval::class, true) + || is_a($subject, DateTimeInterface::class, true) + ) { + continue; + } + + throw new PeriodFilterSafetyException('filters not referring to date method'); + } + + throw new PeriodFilterSafetyException('custom filters'); + } + } + + $this->initializeSerialization($values); + + foreach ($values as $key => $value) { + if ($value === null) { + continue; + } + + $property = match ($key) { + 'tzName' => $this->setTimezone(...), + 'options' => $this->setOptions(...), + 'recurrences' => $this->setRecurrences(...), + 'current' => function (mixed $current): void { + $this->carbonCurrent = $this->carbonOrResolve($current); + }, + 'start' => 'startDate', + 'interval' => $this->setDateInterval(...), + 'end' => 'endDate', + 'key' => null, + 'include_start_date' => function (bool $included): void { + $this->excludeStartDate(!$included); + }, + 'include_end_date' => function (bool $included): void { + $this->excludeEndDate(!$included); + }, + default => $key, + }; + + if ($property === null) { + continue; + } + + if (\is_callable($property)) { + $property($value); + + continue; + } + + if ($value instanceof DateTimeInterface && !($value instanceof CarbonInterface)) { + $value = ($value instanceof DateTime) + ? Carbon::instance($value) + : CarbonImmutable::instance($value); + } + + try { + $this->$property = $value; + } catch (Throwable) { + // Must be ignored for backward-compatibility + } + } + + if (\array_key_exists('carbonRecurrences', $values)) { + $this->carbonRecurrences = $values['carbonRecurrences']; + } elseif (((int) ($values['recurrences'] ?? 0)) <= 1 && $this->endDate !== null) { + $this->carbonRecurrences = null; + } + } catch (Throwable $e) { + // @codeCoverageIgnoreStart + if ( + $e instanceof PeriodFilterSafetyException + || $e instanceof NotACarbonClassException + || !method_exists(parent::class, '__unserialize') + ) { + throw $e; + } + + parent::__unserialize($data); + // @codeCoverageIgnoreEnd + } + } + /** * Update properties after removing built-in filters. - * - * @return void */ - protected function updateInternalState() + protected function updateInternalState(): void { if (!$this->hasFilter(static::END_DATE_FILTER)) { $this->endDate = null; } if (!$this->hasFilter(static::RECURRENCES_FILTER)) { - $this->recurrences = null; + $this->carbonRecurrences = null; } } @@ -2443,12 +2505,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Create a filter tuple from raw parameters. * * Will create an automatic filter callback for one of Carbon's is* methods. - * - * @param array $parameters - * - * @return array */ - protected function createFilterTuple(array $parameters) + protected function createFilterTuple(array $parameters): array { $method = array_shift($parameters); @@ -2456,20 +2514,14 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return [$method, array_shift($parameters)]; } - return [function ($date) use ($method, $parameters) { - return ([$date, $method])(...$parameters); - }, $method]; + return [static fn ($date) => ([$date, $method])(...$parameters), $method]; } /** * Return whether given callable is a string pointing to one of Carbon's is* methods * and should be automatically converted to a filter callback. - * - * @param callable $callable - * - * @return bool */ - protected function isCarbonPredicateMethod($callable) + protected function isCarbonPredicateMethod(callable|string $callable): bool { return \is_string($callable) && str_starts_with($callable, 'is') && (method_exists($this->dateClass, $callable) || ([$this->dateClass, 'hasMacro'])($callable)); @@ -2478,16 +2530,11 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Recurrences filter callback (limits number of recurrences). * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @param \Carbon\Carbon $current - * @param int $key - * - * @return bool|string + * @SuppressWarnings(UnusedFormalParameter) */ - protected function filterRecurrences($current, $key) + protected function filterRecurrences(CarbonInterface $current, int $key): bool|callable { - if ($key < $this->recurrences) { + if ($key < $this->carbonRecurrences) { return true; } @@ -2497,17 +2544,15 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * End date filter callback. * - * @param \Carbon\Carbon $current - * - * @return bool|string + * @return bool|static::END_ITERATION */ - protected function filterEndDate($current) + protected function filterEndDate(CarbonInterface $current): bool|callable { if (!$this->isEndExcluded() && $current == $this->endDate) { return true; } - if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) { + if ($this->dateInterval->invert ? ($current > $this->endDate) : ($current < $this->endDate)) { return true; } @@ -2517,9 +2562,9 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * End iteration filter callback. * - * @return string + * @return static::END_ITERATION */ - protected function endIteration() + protected function endIteration(): callable { return static::END_ITERATION; } @@ -2527,7 +2572,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Handle change of the parameters. */ - protected function handleChangedParameters() + protected function handleChangedParameters(): void { if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) { $this->dateClass = CarbonImmutable::class; @@ -2538,17 +2583,45 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $this->validationResult = null; } + /** + * Synchronize the native DatePeriod properties with the current state. + */ + protected function syncNativePeriod(): void + { + if (\PHP_VERSION_ID < 80200) { + return; // @codeCoverageIgnore + } + + // During construction, the parent DatePeriod is initialized once at the end of __construct(). + if (!$this->constructed) { + return; + } + + // Calling parent::__construct() again on an already initialized DatePeriod leaks its previous + // start, end and interval in PHP, so the native properties are refreshed through + // DatePeriod::__unserialize() instead, which releases them. + // The same native state is then computed as with parent::__construct() so both stay in sync. + $native = new DatePeriod( + $this->startDate, + $this->dateInterval ?? CarbonInterval::day(), + $this->endDate ?? max(1, min(2147483639, $this->recurrences ?? 1)), + $this->options ?? 0, + ); + + parent::__unserialize($native->__serialize()); + } + /** * Validate current date and stop iteration when necessary. * * Returns true when current date is valid, false if it is not, or static::END_ITERATION * when iteration should be stopped. * - * @return bool|string + * @return bool|static::END_ITERATION */ - protected function validateCurrentDate() + protected function validateCurrentDate(): bool|callable { - if ($this->current === null) { + if ($this->carbonCurrent === null) { $this->rewind(); } @@ -2559,19 +2632,14 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Check whether current value and key pass all the filters. * - * @return bool|string + * @return bool|static::END_ITERATION */ - protected function checkFilters() + protected function checkFilters(): bool|callable { - $current = $this->prepareForReturn($this->current); + $current = $this->prepareForReturn($this->carbonCurrent); foreach ($this->filters as $tuple) { - $result = \call_user_func( - $tuple[0], - $current->avoidMutation(), - $this->key, - $this - ); + $result = \call_user_func($tuple[0], $current->avoidMutation(), $this->key, $this); if ($result === static::END_ITERATION) { return static::END_ITERATION; @@ -2597,7 +2665,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable $date = ([$this->dateClass, 'make'])($date); if ($this->timezone) { - $date = $date->setTimezone($this->timezone); + return $date->setTimezone($this->timezone); } return $date; @@ -2607,15 +2675,16 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Keep incrementing the current date until a valid date is found or the iteration is ended. * * @throws RuntimeException - * - * @return void */ - protected function incrementCurrentDateUntilValid() + protected function incrementCurrentDateUntilValid(): void { $attempts = 0; do { - $this->current = $this->current->add($this->dateInterval); + $this->carbonCurrent = $this->carbonCurrent->add( + $this->dateInterval, + $this->dateInterval->getStep() && $this->dateInterval->invert ? -1 : 1, + ); $this->validationResult = null; @@ -2627,13 +2696,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Call given macro. - * - * @param string $name - * @param array $parameters - * - * @return mixed */ - protected function callMacro($name, $parameters) + protected function callMacro(string $name, array $parameters): mixed { $macro = static::$macros[$name]; @@ -2650,7 +2714,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable * Return the Carbon instance passed through, a now instance in the same timezone * if null given or parse the input if string given. * - * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date + * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\Time\Duration|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date * * @return \Carbon\CarbonInterface */ @@ -2661,13 +2725,8 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable /** * Resolve passed arguments or DatePeriod to a CarbonPeriod object. - * - * @param mixed $period - * @param mixed ...$arguments - * - * @return static */ - protected function resolveCarbonPeriod($period, ...$arguments) + protected function resolveCarbonPeriod(mixed $period, mixed ...$arguments): self { if ($period instanceof self) { return $period; @@ -2678,6 +2737,13 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable : static::create($period, ...$arguments); } + private function carbonOrResolve(mixed $dateTime): CarbonInterface + { + return $dateTime instanceof CarbonInterface + ? $dateTime + : $this->resolveCarbon($dateTime); + } + private function orderCouple($first, $second): array { return $first > $second ? [$second, $first] : [$first, $second]; @@ -2689,6 +2755,12 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable return $value; } + if ($value instanceof WeekDay || $value instanceof Month) { + $dateClass = $this->dateClass; + + return new $dateClass($value, $this->timezoneSetting); + } + if (\is_string($value)) { $value = trim($value); @@ -2698,7 +2770,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable ) { $dateClass = $this->dateClass; - return $dateClass::parse($value, $this->tzName); + return $dateClass::parse($value, $this->timezoneSetting); } } @@ -2739,4 +2811,111 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable } } } + + private function setFromAssociativeArray(array $parameters): void + { + if (isset($parameters['start'])) { + $this->setStartDate($parameters['start']); + } + + if (isset($parameters['start'])) { + $this->setStartDate($parameters['start']); + } + + if (isset($parameters['end'])) { + $this->setEndDate($parameters['end']); + } + + if (isset($parameters['recurrences'])) { + $this->setRecurrences($parameters['recurrences']); + } + + if (isset($parameters['interval'])) { + $this->setDateInterval($parameters['interval']); + } + + if (isset($parameters['options'])) { + $this->setOptions($parameters['options']); + } + } + + private function configureTimezone(DateTimeZone $timezone, array $sortedArguments, array $originalArguments): array + { + $this->setTimezone($timezone); + + if (\is_string($originalArguments['start'] ?? null)) { + $sortedArguments['start'] = $this->makeDateTime($originalArguments['start']); + } + + if (\is_string($originalArguments['end'] ?? null)) { + $sortedArguments['end'] = $this->makeDateTime($originalArguments['end']); + } + + return $sortedArguments; + } + + private function initializeSerialization(array $values): void + { + $serializationBase = [ + 'start' => $values['start'] ?? $values['startDate'] ?? null, + 'current' => $values['current'] ?? $values['carbonCurrent'] ?? null, + 'end' => $values['end'] ?? $values['endDate'] ?? null, + 'interval' => $values['interval'] ?? $values['dateInterval'] ?? null, + 'recurrences' => max(1, (int) ($values['recurrences'] ?? $values['carbonRecurrences'] ?? 1)), + 'include_start_date' => $values['include_start_date'] ?? true, + 'include_end_date' => $values['include_end_date'] ?? false, + ]; + + foreach (['start', 'current', 'end'] as $dateProperty) { + if ($serializationBase[$dateProperty] instanceof Carbon) { + $serializationBase[$dateProperty] = $serializationBase[$dateProperty]->toDateTime(); + } elseif ($serializationBase[$dateProperty] instanceof CarbonInterface) { + $serializationBase[$dateProperty] = $serializationBase[$dateProperty]->toDateTimeImmutable(); + } + } + + if ($serializationBase['interval'] instanceof CarbonInterval) { + $serializationBase['interval'] = $serializationBase['interval']->toDateInterval(); + } + + // @codeCoverageIgnoreStart + if (method_exists(parent::class, '__unserialize')) { + parent::__unserialize($serializationBase); + + return; + } + + $excludeStart = !($values['include_start_date'] ?? true); + $includeEnd = $values['include_end_date'] ?? true; + + parent::__construct( + $serializationBase['start'], + $serializationBase['interval'], + $serializationBase['end'] ?? $serializationBase['recurrences'], + ($excludeStart ? self::EXCLUDE_START_DATE : 0) | ($includeEnd && \defined('DatePeriod::INCLUDE_END_DATE') ? self::INCLUDE_END_DATE : 0), + ); + // @codeCoverageIgnoreEnd + } + + private static function addFilterOrHandleChangedParameters( + self $period, + array|callable|string $filter, + ): self { + if (!$period->hasFilter($filter)) { + return $period->addFilter($filter); + } + + $period->handleChangedParameters(); + + return $period; + } + + private static function makeCarbonInterval(mixed $interval, Unit|string|null $unit = null): ?CarbonInterval + { + if ($unit === null && $interval instanceof CarbonInterval) { + return clone $interval; + } + + return CarbonInterval::make($interval, $unit); + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php index f0d0ee281..0e8ff28e2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonPeriodImmutable.php @@ -1,5 +1,7 @@ constructed ? clone $this : $this; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php index c81899f1e..a7690fbe6 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php @@ -1,5 +1,7 @@ initLocalFactory(); + parent::__construct(static::getDateTimeZoneNameFromMixed($timezone)); } - protected static function parseNumericTimezone($timezone) + protected static function parseNumericTimezone(string|int|float $timezone): string { - if ($timezone <= -100 || $timezone >= 100) { - throw new InvalidTimeZoneException('Absolute timezone offset cannot be greater than 100.'); + if (abs((float) $timezone) > static::MAXIMUM_TIMEZONE_OFFSET) { + throw new InvalidTimeZoneException( + 'Absolute timezone offset cannot be greater than '. + static::MAXIMUM_TIMEZONE_OFFSET.'.', + ); } - return ($timezone >= 0 ? '+' : '').ltrim($timezone, '+').':00'; + return ($timezone >= 0 ? '+' : '').ltrim((string) $timezone, '+').':00'; } - protected static function getDateTimeZoneNameFromMixed($timezone) + protected static function getDateTimeZoneNameFromMixed(string|int|float $timezone): string { - if ($timezone === null) { - return date_default_timezone_get(); - } - if (\is_string($timezone)) { $timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone); } @@ -50,19 +61,14 @@ class CarbonTimeZone extends DateTimeZone return $timezone; } - protected static function getDateTimeZoneFromName(&$name) - { - return @timezone_open($name = (string) static::getDateTimeZoneNameFromMixed($name)); - } - /** * Cast the current instance into the given class. * - * @param string $className The $className::instance() method will be called to cast the current object. + * @param class-string $className The $className::instance() method will be called to cast the current object. * - * @return DateTimeZone + * @return DateTimeZone|mixed */ - public function cast(string $className) + public function cast(string $className): mixed { if (!method_exists($className, 'instance')) { if (is_a($className, DateTimeZone::class, true)) { @@ -78,38 +84,40 @@ class CarbonTimeZone extends DateTimeZone /** * Create a CarbonTimeZone from mixed input. * - * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. - * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * @param DateTimeZone|string|int|false|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|false|null $objectDump dump of the object for error messages. * * @throws InvalidTimeZoneException * - * @return false|static + * @return static|null */ - public static function instance($object = null, $objectDump = null) - { - $tz = $object; + public static function instance( + DateTimeZone|string|int|false|null $object, + DateTimeZone|string|int|false|null $objectDump = null, + ): ?self { + $timezone = $object; - if ($tz instanceof static) { - return $tz; + if ($timezone instanceof static) { + return $timezone; } - if ($tz === null) { - return new static(); + if ($timezone === null || $timezone === false) { + return null; } - if (!$tz instanceof DateTimeZone) { - $tz = static::getDateTimeZoneFromName($object); - } + try { + if (!($timezone instanceof DateTimeZone)) { + $name = static::getDateTimeZoneNameFromMixed($object); + $timezone = new static($name); + } - if ($tz !== false) { - return new static($tz->getName()); + return $timezone instanceof static ? $timezone : new static($timezone->getName()); + } catch (Exception $exception) { + throw new InvalidTimeZoneException( + 'Unknown or bad timezone ('.($objectDump ?: $object).')', + previous: $exception, + ); } - - if (Carbon::isStrictModeEnabled()) { - throw new InvalidTimeZoneException('Unknown or bad timezone ('.($objectDump ?: $object).')'); - } - - return false; } /** @@ -119,14 +127,27 @@ class CarbonTimeZone extends DateTimeZone * * @return string */ - public function getAbbreviatedName($dst = false) + public function getAbbreviatedName(bool $dst = false): string { $name = $this->getName(); - foreach ($this->listAbbreviations() as $abbreviation => $zones) { + $date = new DateTimeImmutable($dst ? 'July 1' : 'January 1', $this); + $timezone = $date->format('T'); + $abbreviations = $this->listAbbreviations(); + $matchingZones = array_merge($abbreviations[$timezone] ?? [], $abbreviations[strtolower($timezone)] ?? []); + + if ($matchingZones !== []) { + foreach ($matchingZones as $zone) { + if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { + return $timezone; + } + } + } + + foreach ($abbreviations as $abbreviation => $zones) { foreach ($zones as $zone) { if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) { - return $abbreviation; + return strtoupper($abbreviation); } } } @@ -143,33 +164,25 @@ class CarbonTimeZone extends DateTimeZone * * @return string */ - public function getAbbr($dst = false) + public function getAbbr(bool $dst = false): string { return $this->getAbbreviatedName($dst); } /** * Get the offset as string "sHH:MM" (such as "+00:00" or "-12:30"). - * - * @param DateTimeInterface|null $date - * - * @return string */ - public function toOffsetName(DateTimeInterface $date = null) + public function toOffsetName(?DateTimeInterface $date = null): string { return static::getOffsetNameFromMinuteOffset( - $this->getOffset($date ?: Carbon::now($this)) / 60 + $this->getOffset($this->resolveCarbon($date)) / 60, ); } /** * Returns a new CarbonTimeZone object using the offset string instead of region string. - * - * @param DateTimeInterface|null $date - * - * @return CarbonTimeZone */ - public function toOffsetTimeZone(DateTimeInterface $date = null) + public function toOffsetTimeZone(?DateTimeInterface $date = null): static { return new static($this->toOffsetName($date)); } @@ -179,13 +192,8 @@ class CarbonTimeZone extends DateTimeZone * false if no match is found. * * @see timezone_name_from_abbr native PHP function. - * - * @param DateTimeInterface|null $date - * @param int $isDst - * - * @return string|false */ - public function toRegionName(DateTimeInterface $date = null, $isDst = 1) + public function toRegionName(?DateTimeInterface $date = null, int $isDST = 1): ?string { $name = $this->getName(); $firstChar = substr($name, 0, 1); @@ -194,52 +202,48 @@ class CarbonTimeZone extends DateTimeZone return $name; } - $date = $date ?: Carbon::now($this); + $date = $this->resolveCarbon($date); // Integer construction no longer supported since PHP 8 // @codeCoverageIgnoreStart try { $offset = @$this->getOffset($date) ?: 0; - } catch (Throwable $e) { + } catch (Throwable) { $offset = 0; } // @codeCoverageIgnoreEnd - $name = @timezone_name_from_abbr('', $offset, $isDst); + $name = @timezone_name_from_abbr('', $offset, $isDST); if ($name) { return $name; } foreach (timezone_identifiers_list() as $timezone) { - if (Carbon::instance($date)->tz($timezone)->getOffset() === $offset) { + if (Carbon::instance($date)->setTimezone($timezone)->getOffset() === $offset) { return $timezone; } } - return false; + return null; } /** * Returns a new CarbonTimeZone object using the region string instead of offset string. - * - * @param DateTimeInterface|null $date - * - * @return CarbonTimeZone|false */ - public function toRegionTimeZone(DateTimeInterface $date = null) + public function toRegionTimeZone(?DateTimeInterface $date = null): ?self { - $tz = $this->toRegionName($date); + $timezone = $this->toRegionName($date); - if ($tz !== false) { - return new static($tz); + if ($timezone !== null) { + return new static($timezone); } if (Carbon::isStrictModeEnabled()) { - throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($date ?: Carbon::now($this)).' seconds.'); + throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($this->resolveCarbon($date)).' seconds.'); } - return false; + return null; } /** @@ -285,7 +289,7 @@ class CarbonTimeZone extends DateTimeZone */ public static function createFromHourOffset(float $hourOffset) { - return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR); + return static::createFromMinuteOffset($hourOffset * UnitValue::MINUTES_PER_HOUR); } /** @@ -317,4 +321,17 @@ class CarbonTimeZone extends DateTimeZone ':'. str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT); } + + private function resolveCarbon(?DateTimeInterface $date): DateTimeInterface + { + if ($date) { + return $date; + } + + if (isset($this->clock)) { + return $this->clock->now()->setTimezone($this); + } + + return Carbon::now($this); + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php index 4f35d6c61..3aabe3f63 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Cli/Invoker.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Constants; + +interface DiffOptions +{ + /** + * Diff wording options(expressed in octal). + */ + public const NO_ZERO_DIFF = 01; + public const JUST_NOW = 02; + public const ONE_DAY_WORDS = 04; + public const TWO_DAY_WORDS = 010; + public const SEQUENTIAL_PARTS_ONLY = 020; + public const ROUND = 040; + public const FLOOR = 0100; + public const CEIL = 0200; + + /** + * Diff syntax options. + */ + public const DIFF_ABSOLUTE = 1; // backward compatibility with true + public const DIFF_RELATIVE_AUTO = 0; // backward compatibility with false + public const DIFF_RELATIVE_TO_NOW = 2; + public const DIFF_RELATIVE_TO_OTHER = 3; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/Format.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/Format.php new file mode 100644 index 000000000..6e4382794 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/Format.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Constants; + +interface Format +{ + /** + * RFC7231 DateTime format. + * + * @var string + */ + public const RFC7231_FORMAT = 'D, d M Y H:i:s \G\M\T'; + + /** + * Default format to use for __toString method when type juggling occurs. + * + * @var string + */ + public const DEFAULT_TO_STRING_FORMAT = 'Y-m-d H:i:s'; + + /** + * Format for converting mocked time, includes microseconds. + * + * @var string + */ + public const MOCK_DATETIME_FORMAT = 'Y-m-d H:i:s.u'; + + /** + * Pattern detection for ->isoFormat and ::createFromIsoFormat. + * + * @var string + */ + public const ISO_FORMAT_REGEXP = '(O[YMDHhms]|[Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY?|N{1,5}|yo|y{1,4}|g{1,5}|G{1,5}|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?)'; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/TranslationOptions.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/TranslationOptions.php new file mode 100644 index 000000000..ddb207cde --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/TranslationOptions.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Constants; + +interface TranslationOptions +{ + /** + * Translate string options. + */ + public const TRANSLATE_MONTHS = 1; + public const TRANSLATE_DAYS = 2; + public const TRANSLATE_UNITS = 4; + public const TRANSLATE_MERIDIEM = 8; + public const TRANSLATE_DIFF = 0x10; + public const TRANSLATE_ALL = self::TRANSLATE_MONTHS | self::TRANSLATE_DAYS | self::TRANSLATE_UNITS | self::TRANSLATE_MERIDIEM | self::TRANSLATE_DIFF; + + /** + * Special settings to get the start of week from current locale culture. + */ + public const WEEK_DAY_AUTO = 'auto'; + + /** + * Default locale (language and region). + * + * @var string + */ + public const DEFAULT_LOCALE = 'en'; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/UnitValue.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/UnitValue.php new file mode 100644 index 000000000..9fb5acf70 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Constants/UnitValue.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Constants; + +interface UnitValue +{ + /** + * The day constants. + */ + public const SUNDAY = 0; + public const MONDAY = 1; + public const TUESDAY = 2; + public const WEDNESDAY = 3; + public const THURSDAY = 4; + public const FRIDAY = 5; + public const SATURDAY = 6; + + /** + * The month constants. + * These aren't used by Carbon itself but exist for + * convenience's sake alone. + */ + public const JANUARY = 1; + public const FEBRUARY = 2; + public const MARCH = 3; + public const APRIL = 4; + public const MAY = 5; + public const JUNE = 6; + public const JULY = 7; + public const AUGUST = 8; + public const SEPTEMBER = 9; + public const OCTOBER = 10; + public const NOVEMBER = 11; + public const DECEMBER = 12; + + /** + * Number of X in Y. + */ + public const YEARS_PER_MILLENNIUM = 1_000; + public const YEARS_PER_CENTURY = 100; + public const YEARS_PER_DECADE = 10; + public const MONTHS_PER_YEAR = 12; + public const MONTHS_PER_QUARTER = 3; + public const QUARTERS_PER_YEAR = 4; + public const WEEKS_PER_YEAR = 52; + public const WEEKS_PER_MONTH = 4; + public const DAYS_PER_YEAR = 365; + public const DAYS_PER_WEEK = 7; + public const HOURS_PER_DAY = 24; + public const MINUTES_PER_HOUR = 60; + public const SECONDS_PER_MINUTE = 60; + public const MILLISECONDS_PER_SECOND = 1_000; + public const MICROSECONDS_PER_MILLISECOND = 1_000; + public const MICROSECONDS_PER_SECOND = 1_000_000; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/DurationConverter.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/DurationConverter.php new file mode 100644 index 000000000..11ff0aea5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/DurationConverter.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Constants\UnitValue; +use Carbon\Exceptions\InvalidIntervalException; +use DateInterval; +use Time\Duration; + +/** @internal */ +final class DurationConverter +{ + public static function getDurationUnits(Duration $duration): array + { + $microseconds = intdiv($duration->nanoseconds, 1000); + $seconds = $duration->seconds; + $minutes = intdiv($seconds, UnitValue::SECONDS_PER_MINUTE); + $seconds %= UnitValue::SECONDS_PER_MINUTE; + $hours = intdiv($minutes, UnitValue::MINUTES_PER_HOUR); + $minutes %= UnitValue::MINUTES_PER_HOUR; + + return [$hours, $minutes, $seconds, $microseconds]; + } + + public static function getDurationIsoSpec(Duration $duration): string + { + [$hours, $minutes, $seconds] = self::getDurationUnits($duration); + + return "PT{$hours}H{$minutes}M{$seconds}S"; + } + + public static function createDurationFromInterval(DateInterval|Duration $interval): Duration + { + if ($interval instanceof Duration) { + return clone $interval; + } + + if ($interval->y || $interval->m || $interval->days || $interval->d) { + throw new InvalidIntervalException( + 'Date units (years, months, weeks and days) cannot be converted to '.Duration::class. + ' because they cannot be unambiguously converted to elapsed time.' + ); + } + + if (abs($interval->f) >= 1) { + throw new InvalidIntervalException( + "Interval with number of microseconds higher than 999'999 cannot be converted to ". + Duration::class, + ); + } + + return self::createDurationFromSeconds( + (($interval->h * UnitValue::MINUTES_PER_HOUR) + $interval->i) * UnitValue::SECONDS_PER_MINUTE + $interval->s, + (int) ($interval->f * 1_000_000_000), + (bool) $interval->invert, + ); + } + + public static function createDurationFromSeconds(int $seconds, int $nanoseconds = 0, bool $inverted = false): Duration + { + if ($seconds < 0) { + $seconds = abs($seconds); + $inverted = !$inverted; + $nanoseconds *= -1; + } + + if ($nanoseconds < 0) { + $seconds--; + $nanoseconds += 1_000_000_000; + } + + $duration = Duration::fromSeconds($seconds, $nanoseconds); + + return $inverted ? $duration->negate() : $duration; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php index 3ca8837d1..db1ea8e99 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadComparisonUnitException.php @@ -1,5 +1,7 @@ unit = $unit; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php index 2e222e54e..e8cded033 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentConstructorException.php @@ -1,5 +1,7 @@ method = $method; - parent::__construct(sprintf("Unknown fluent constructor '%s'.", $method), $code, $previous); + parent::__construct(\sprintf("Unknown fluent constructor '%s'.", $method), $code, $previous); } /** diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php index 4ceaa2ef0..b3111bff0 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadFluentSetterException.php @@ -1,5 +1,7 @@ setter = $setter; - parent::__construct(sprintf("Unknown fluent setter '%s'", $setter), $code, $previous); + parent::__construct(\sprintf("Unknown fluent setter '%s'", $setter), $code, $previous); } /** diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php index 108206d3e..3f10af992 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/BadMethodCallException.php @@ -1,5 +1,7 @@ value = $value; - parent::__construct("$value is immutable.", $code, $previous); + $this->value ??= $value ?? $message; + + parent::__construct($message, $code, $previous); + } + + public static function fromClass(string $class, int $code = 0, ?Throwable $previous = null): self + { + return new self("$class class is immutable.", $code, $previous, "$class class"); + } + + public static function fromMethod(string $class, string $method, int $code = 0, ?Throwable $previous = null): self + { + return new self("$method not allowed on $class.", $code, $previous, "$class::$method method"); } - /** - * Get the value. - * - * @return string - */ public function getValue(): string { return $this->value; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php index 5b013cd50..7fb5a991d 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidArgumentException.php @@ -1,5 +1,7 @@ field = $field; $this->value = $value; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php index 92d55fe35..ffc5f212f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/InvalidFormatException.php @@ -1,5 +1,7 @@ className = $className; - parent::__construct(sprintf('Given class does not implement %s: %s', CarbonInterface::class, $className), $code, $previous); + parent::__construct( + \sprintf( + 'Given class does not implement %s: %s', + CarbonInterface::class, + $className, + )."\nBehavior can be unpredictable and unsecure ". + "(in particular if you are unserializing data from a source you can't fully trust)\n". + "But if you're sure you want to allow it use \$result = ". + 'NotACarbonClassException::allow(static fn () => ...)', + $code, + $previous, + ); + } + + public static function expectCarbonInterface(mixed $className): void + { + if (!self::$detectionEnabled) { + return; + } + + if (!\is_string($className)) { + throw new self(\gettype($className)); + } + + if (!is_a($className, CarbonInterface::class, true)) { + throw new self($className); + } } /** diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php index 4edd7a484..23e09a286 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/NotAPeriodException.php @@ -1,5 +1,7 @@ unit = $unit; $this->min = $min; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php index 5416fd149..556bfedeb 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/ParseErrorException.php @@ -1,5 +1,7 @@ expected = $expected; $this->actual = $actual; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/PeriodFilterSafetyException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/PeriodFilterSafetyException.php new file mode 100644 index 000000000..95ffa1fd2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/PeriodFilterSafetyException.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Carbon\Traits\TogglableDetection; +use RuntimeException as BaseRuntimeException; +use Throwable; + +final class PeriodFilterSafetyException extends BaseRuntimeException implements RuntimeException +{ + use TogglableDetection; + + public function __construct( + public string $disallowedFilter, + int $code = 0, + ?Throwable $previous = null, + ) { + parent::__construct( + "For safety reason, unserializing CarbonPeriod objects with $disallowedFilter is disallowed.\n". + "If your serialized string is fully safe and you're sure you want to allow them, wrap the unseritalization". + ' into $result = PeriodFilterSafetyException::allow(static fn () => unserialize($data))', + $code, + $previous, + ); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php index ad196f79d..85bfb142b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/RuntimeException.php @@ -1,5 +1,7 @@ unit = $unit; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php index 5c504975a..982a30820 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownGetterException.php @@ -1,5 +1,7 @@ getter = $getter; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php index 75273a706..c72c368cf 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownMethodException.php @@ -1,5 +1,7 @@ method = $method; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php index a795f5d72..e97db4bb2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownSetterException.php @@ -1,5 +1,7 @@ setter = $setter; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php index ecd7f7a59..833c4d73c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnknownUnitException.php @@ -1,5 +1,7 @@ unit = $unit; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php index 1654ab11b..c637d3b20 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Exceptions/UnreachableException.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Exceptions; + +use Exception; + +/** + * @codeCoverageIgnore + */ +class UnsupportedUnitException extends UnitException +{ + public function __construct(string $unit, int $code = 0, ?Exception $previous = null) + { + parent::__construct("Unsupported unit '$unit'", $code, $previous); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php index d497535f7..c9352fb67 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Factory.php @@ -1,5 +1,7 @@ * - * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a - * new instance. - * @method Carbon|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new Carbon instance from a specific date and time. - * If any of $year, $month or $day are set to null their now() values will - * be used. - * If $hour is null it will be set to its now() value and the default - * values for $minute and $second will be their now() values. - * If $hour is not null then the default values for $minute and $second - * will be 0. - * @method Carbon createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. - * @method Carbon|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. - * @method Carbon|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). - * @method Carbon|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. - * @method Carbon|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. - * @method Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. - * @method Carbon createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. - * @method Carbon createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method Carbon createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method Carbon createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method Carbon createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method Carbon createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. - * @method Carbon|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe Carbon instance from a specific date and time. - * If any of $year, $month or $day are set to null their now() values will - * be used. - * If $hour is null it will be set to its now() value and the default - * values for $minute and $second will be their now() values. - * If $hour is not null then the default values for $minute and $second - * will be 0. - * If one of the set values is not valid, an InvalidDateException - * will be thrown. - * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. - * @method Carbon disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method Carbon enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, - * then return the result of the closure (or null if the closure was void). - * @method Carbon fromSerialized($value) Create an instance from a serialized string. - * @method void genericMacro($macro, $priority = 0) Register a custom macro. - * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. - * (It will ignore custom translator dynamic loading.) - * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native - * name, region and variant of the locale. - * @method array getDays() Get the days of the week - * @method string|null getFallbackLocale() Get the fallback locale. - * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). - * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). - * @method array getIsoUnits() Returns list of locale units for ISO formatting. - * @method array getLastErrors() {@inheritdoc} - * @method string getLocale() Get the current translator locale. - * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. - * @method int getMidDayAt() get midday/noon hour - * @method Closure|Carbon getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" - * instance is created. - * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. - * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. - * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. - * @method int getWeekEndsAt() Get the last day of week - * @method int getWeekStartsAt() Get the first day of week - * @method array getWeekendDays() Get weekend days - * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. - * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. - * @method bool hasMacro($name) Checks if macro is registered globally. - * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. - * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance - * is anything that is not null. - * @method Carbon instance($date) Create a Carbon instance from a DateTime one. - * @method bool isImmutable() Returns true if the current class/instance is immutable. - * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. - * @method bool isMutable() Returns true if the current class/instance is mutable. - * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. - * (It can be overridden in specific instances.) - * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). - * Support is considered enabled if the 3 words are translated in the given locale. - * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). - * Support is considered enabled if the 4 sentences are translated in the given locale. - * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). - * Support is considered enabled if the 2 words are translated in the given locale. - * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). - * Support is considered enabled if the 4 sentences are translated in the given locale. - * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. - * Support is considered enabled if either year, day or hour has a short variant translated. - * @method void macro($name, $macro) Register a custom macro. - * @method Carbon|null make($var) Make a Carbon instance from given variable if possible. - * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals - * and recurrences). Throw an exception for invalid format, but otherwise return null. - * @method Carbon maxValue() Create a Carbon instance for the greatest supported date. - * @method Carbon minValue() Create a Carbon instance for the lowest supported date. - * @method void mixin($mixin) Mix another object into the class. - * @method Carbon now($tz = null) Get a Carbon instance for the current date and time. - * @method Carbon parse($time = null, $tz = null) Create a carbon instance from a string. - * This is an alias for the constructor that allows better fluent syntax - * as it allows you to do Carbon::parse('Monday next week')->fn() rather - * than (new Carbon('Monday next week'))->fn(). - * @method Carbon parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). - * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). - * @method Carbon|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. - * @method Carbon rawParse($time = null, $tz = null) Create a carbon instance from a string. - * This is an alias for the constructor that allows better fluent syntax - * as it allows you to do Carbon::parse('Monday next week')->fn() rather - * than (new Carbon('Monday next week'))->fn(). - * @method Carbon resetMacros() Remove all macros and generic macros. - * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string - * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather transform Carbon object before the serialization. - * JSON serialize all Carbon instances using the given callback. - * @method Carbon setFallbackLocale($locale) Set the fallback locale. - * @method Carbon setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. - * Pass 'auto' as locale to use closest language from the current LC_TIME locale. - * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather consider mid-day is always 12pm, then if you need to test if it's an other - * hour, test it explicitly: - * $date->format('G') == 13 - * or to set explicitly to a given hour: - * $date->setTime(13, 0, 0, 0) - * Set midday/noon hour - * @method Carbon setTestNow($testNow = null) Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned - * specifically under the following conditions: - * - A call to the static now() method, ex. Carbon::now() - * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) - * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') - * - When a string containing the desired time is passed to Carbon::parse(). - * Note the timezone parameter was left out of the examples above and - * has no affect as the mock value will be returned regardless of its value. - * Only the moment is mocked with setTestNow(), the timezone will still be the one passed - * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). - * To clear the test instance call this method using the default - * parameter of null. - * /!\ Use this method for unit tests only. - * @method Carbon setTestNowAndTimezone($testNow = null, $tz = null) Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned - * specifically under the following conditions: - * - A call to the static now() method, ex. Carbon::now() - * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) - * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') - * - When a string containing the desired time is passed to Carbon::parse(). - * It will also align default timezone (e.g. call date_default_timezone_set()) with - * the second argument or if null, with the timezone of the given date object. - * To clear the test instance call this method using the default - * parameter of null. - * /!\ Use this method for unit tests only. - * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and - * use other method or custom format passed to format() method if you need to dump another string - * format. - * Set the default format used when type juggling a Carbon instance to a string. - * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. - * @method Carbon setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use UTF-8 language packages on every machine. - * Set if UTF8 will be used for localized date/time. - * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek - * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the - * start of week according to current locale selected and implicitly the end of week. - * Set the last day of week - * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the - * 'first_day_of_week' locale setting to change the start of week according to current locale - * selected and implicitly the end of week. - * Set the first day of week - * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather consider week-end is always saturday and sunday, and if you have some custom - * week-end days to handle, give to those days an other name and create a macro for them: - * ``` - * Carbon::macro('isDayOff', function ($date) { - * return $date->isSunday() || $date->isMonday(); - * }); - * Carbon::macro('isNotDayOff', function ($date) { - * return !$date->isDayOff(); - * }); - * if ($someDate->isDayOff()) ... - * if ($someDate->isNotDayOff()) ... - * // Add 5 not-off days - * $count = 5; - * while ($someDate->isDayOff() || ($count-- > 0)) { - * $someDate->addDay(); - * } - * ``` - * Set weekend days - * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). - * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). - * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). - * @method Carbon today($tz = null) Create a Carbon instance for today. - * @method Carbon tomorrow($tz = null) Create a Carbon instance for tomorrow. - * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. - * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. - * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method Carbon useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. - * Using setTestNow to set the date, executing the callback, then - * clearing the test instance. - * /!\ Use this method for unit tests only. - * @method Carbon yesterday($tz = null) Create a Carbon instance for yesterday. + * @method bool canBeCreatedFromFormat(?string $date, string $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method ?Carbon create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null) Create a new Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method Carbon createFromDate($year = null, $month = null, $day = null, $timezone = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method ?Carbon createFromFormat($format, $time, $timezone = null) Create a Carbon instance from a specific format. + * @method ?Carbon createFromIsoFormat(string $format, string $time, $timezone = null, ?string $locale = 'en', ?TranslatorInterface $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * Era tokens (N to NNNNN, y, yy, yyy, yyyy, yo) are read with the "eras" of $locale: the era name gives the + * era and the year is the year of this era, so 'D MMMM N y' with 'th' reads '29 กุมภาพันธ์ พ.ศ. 2567' as + * 2024-02-29. Without any N token, the era of today's date in this locale is used. + * @method ?Carbon createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null) Create a Carbon instance from a specific format and a string in a given language. + * @method ?Carbon createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method Carbon createFromTime($hour = 0, $minute = 0, $second = 0, $timezone = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method Carbon createFromTimeString(string $time, DateTimeZone|string|int|null $timezone = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method Carbon createFromTimestamp(string|int|float $timestamp, DateTimeZone|string|int|null $timezone = null) Create a Carbon instance from a timestamp and set the timezone (UTC by default). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMs(string|int|float $timestamp, DateTimeZone|string|int|null $timezone = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createFromTimestampUTC(string|int|float $timestamp) Create a Carbon instance from a timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method Carbon createMidnightDate($year = null, $month = null, $day = null, $timezone = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method ?Carbon createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null) Create a new safe Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method Carbon createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $timezone = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method mixed executeWithLocale(string $locale, callable $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method Carbon fromSerialized($value, array $options = []) Create an instance from a serialized string. + * Unserializing object can lead to arbitrary execution of period filters, and assignation + * of unvalidated values to properties. + * You should never unserialize a value from not from a trusted source. Ensure you unserialize + * only strings coming from storage no unauthorized party can read nor write, prefer to exchange + * data with other systems via static formats such as JSON. + * If the data has to transit via untrusted systems before unserialization, then you should + * sign it cryptographically (with HMAC or OpenSSL for example), and verify the signature + * before unserializing it. + * If the content of $value is unknown, consider using the allowed_classes option to limit + * the types of objects that can be built, for instance: + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method array getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week. + * @method ?string getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array|false getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method int getMidDayAt() get midday/noon hour + * @method string getTimeFormatByPrecision(string $unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string|Closure|null getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method int getWeekEndsAt(?string $locale = null) Get the last day of week. + * @method int getWeekStartsAt(?string $locale = null) Get the first day of week. + * @method bool hasRelativeKeywords(?string $time) Determine if a time string will produce a relative date. + * @method Carbon instance(DateTimeInterface $date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool localeHasDiffOneDayWords(string $locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax(string $locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords(string $locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits(string $locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method ?Carbon make($var, DateTimeZone|string|null $timezone = null) Make a Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method void mixin(object|string $mixin) Mix another object into the class. + * @method Carbon now(DateTimeZone|string|int|null $timezone = null) Get a Carbon instance for the current date and time. + * @method Carbon parse(DateTimeInterface|WeekDay|Month|string|int|float|null $time, DateTimeZone|string|int|null $timezone = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method Carbon parseFromLocale(string $time, ?string $locale = null, DateTimeZone|string|int|null $timezone = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method ?Carbon rawCreateFromFormat(string $format, string $time, $timezone = null) Create a Carbon instance from a specific format. + * @method Carbon rawParse(DateTimeInterface|WeekDay|Month|string|int|float|null $time, DateTimeZone|string|int|null $timezone = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method void setFallbackLocale(string $locale) Set the fallback locale. + * @method void setLocale(string $locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use the closest language to the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method void sleep(int|float $seconds) + * @method Carbon today(DateTimeZone|string|int|null $timezone = null) Create a Carbon instance for today. + * @method Carbon tomorrow(DateTimeZone|string|int|null $timezone = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString(string $timeString, ?string $from = null, ?string $to = null, int $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method Carbon yesterday(DateTimeZone|string|int|null $timezone = null) Create a Carbon instance for yesterday. * * */ class Factory { - protected $className = Carbon::class; + protected string $className = Carbon::class; - protected $settings = []; + protected array $settings = []; + + /** + * A test Carbon instance to be returned when now instances are created. + */ + protected Closure|CarbonInterface|null $testNow = null; + + /** + * The timezone to restore to when clearing the time mock. + */ + protected ?string $testDefaultTimezone = null; + + /** + * Is true when test-now is generated by a closure and timezone should be taken on the fly from it. + */ + protected bool $useTimezoneFromTestNow = false; + + /** + * Default translator. + */ + protected TranslatorInterface $translator; + + /** + * Days of weekend. + */ + protected array $weekendDays = [ + CarbonInterface::SATURDAY, + CarbonInterface::SUNDAY, + ]; + + /** + * Format regex patterns. + * + * @var array + */ + protected array $regexFormats = [ + 'd' => '(3[01]|[12][0-9]|0[1-9])', + 'D' => '(Sun|Mon|Tue|Wed|Thu|Fri|Sat)', + 'j' => '([123][0-9]|[1-9])', + 'l' => '([a-zA-Z]{2,})', + 'N' => '([1-7])', + 'S' => '(st|nd|rd|th)', + 'w' => '([0-6])', + 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', + 'W' => '(5[012]|[1-4][0-9]|0?[1-9])', + 'F' => '([a-zA-Z]{2,})', + 'm' => '(1[012]|0[1-9])', + 'M' => '([a-zA-Z]{3})', + 'n' => '(1[012]|[1-9])', + 't' => '(2[89]|3[01])', + 'L' => '(0|1)', + 'o' => '([1-9][0-9]{0,4})', + 'Y' => '([1-9]?[0-9]{4})', + 'y' => '([0-9]{2})', + 'a' => '(am|pm)', + 'A' => '(AM|PM)', + 'B' => '([0-9]{3})', + 'g' => '(1[012]|[1-9])', + 'G' => '(2[0-3]|1?[0-9])', + 'h' => '(1[012]|0[1-9])', + 'H' => '(2[0-3]|[01][0-9])', + 'i' => '([0-5][0-9])', + 's' => '([0-5][0-9])', + 'u' => '([0-9]{1,6})', + 'v' => '([0-9]{1,3})', + 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\\/[a-zA-Z]*)', + 'I' => '(0|1)', + 'O' => '([+-](1[0123]|0[0-9])[0134][05])', + 'P' => '([+-](1[0123]|0[0-9]):[0134][05])', + 'p' => '(Z|[+-](1[0123]|0[0-9]):[0134][05])', + 'T' => '([a-zA-Z]{1,5})', + 'Z' => '(-?[1-5]?[0-9]{1,4})', + 'U' => '([0-9]*)', + + // The formats below are combinations of the above formats. + 'c' => '(([1-9]?[0-9]{4})-(1[012]|0[1-9])-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[+-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP + 'r' => '(([a-zA-Z]{3}), ([123][0-9]|0[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [+-](1[012]|0[0-9])([0134][05]))', // D, d M Y H:i:s O + ]; + + /** + * Format modifiers (such as available in createFromFormat) regex patterns. + * + * @var array + */ + protected array $regexFormatModifiers = [ + '*' => '.+', + ' ' => '[ ]', + '#' => '[;:\\/.,()-]', + '?' => '([^a]|[a])', + '!' => '', + '|' => '', + '+' => '', + ]; public function __construct(array $settings = [], ?string $className = null) { @@ -254,56 +253,505 @@ class Factory $this->settings = $settings; } - public function getClassName() + public function getClassName(): string { return $this->className; } - public function setClassName(string $className) + public function setClassName(string $className): self { $this->className = $className; return $this; } - public function className(string $className = null) + public function className(?string $className = null): self|string { return $className === null ? $this->getClassName() : $this->setClassName($className); } - public function getSettings() + public function getSettings(): array { return $this->settings; } - public function setSettings(array $settings) + public function setSettings(array $settings): self { $this->settings = $settings; return $this; } - public function settings(array $settings = null) + public function settings(?array $settings = null): self|array { return $settings === null ? $this->getSettings() : $this->setSettings($settings); } - public function mergeSettings(array $settings) + public function mergeSettings(array $settings): self { $this->settings = array_merge($this->settings, $settings); return $this; } - public function __call($name, $arguments) + public function setHumanDiffOptions(int $humanDiffOptions): void + { + $this->mergeSettings([ + 'humanDiffOptions' => $humanDiffOptions, + ]); + } + + public function enableHumanDiffOption($humanDiffOption): void + { + $this->setHumanDiffOptions($this->getHumanDiffOptions() | $humanDiffOption); + } + + public function disableHumanDiffOption(int $humanDiffOption): void + { + $this->setHumanDiffOptions($this->getHumanDiffOptions() & ~$humanDiffOption); + } + + public function getHumanDiffOptions(): int + { + return (int) ($this->getSettings()['humanDiffOptions'] ?? 0); + } + + /** + * Register a custom macro. + * + * Pass null macro to remove it. + * + * @example + * ``` + * $userSettings = [ + * 'locale' => 'pt', + * 'timezone' => 'America/Sao_Paulo', + * ]; + * $factory->macro('userFormat', function () use ($userSettings) { + * return $this->copy()->locale($userSettings['locale'])->tz($userSettings['timezone'])->calendar(); + * }); + * echo $factory->yesterday()->hours(11)->userFormat(); + * ``` + * + * @param-closure-this static $macro + */ + public function macro(string $name, ?callable $macro): void + { + $macros = $this->getSettings()['macros'] ?? []; + $macros[$name] = $macro; + + $this->mergeSettings([ + 'macros' => $macros, + ]); + } + + /** + * Remove all macros and generic macros. + */ + public function resetMacros(): void + { + $this->mergeSettings([ + 'macros' => null, + 'genericMacros' => null, + ]); + } + + /** + * Register a custom macro. + * + * @param callable $macro + * @param int $priority marco with higher priority is tried first + * + * @return void + */ + public function genericMacro(callable $macro, int $priority = 0): void + { + $genericMacros = $this->getSettings()['genericMacros'] ?? []; + + if (!isset($genericMacros[$priority])) { + $genericMacros[$priority] = []; + krsort($genericMacros, SORT_NUMERIC); + } + + $genericMacros[$priority][] = $macro; + + $this->mergeSettings([ + 'genericMacros' => $genericMacros, + ]); + } + + /** + * Checks if macro is registered globally. + */ + public function hasMacro(string $name): bool + { + return isset($this->getSettings()['macros'][$name]); + } + + /** + * Get the raw callable macro registered globally for a given name. + */ + public function getMacro(string $name): ?callable + { + return $this->getSettings()['macros'][$name] ?? null; + } + + /** + * Set the default translator instance to use. + */ + public function setTranslator(TranslatorInterface $translator): void + { + $this->translator = $translator; + } + + /** + * Initialize the default translator instance if necessary. + */ + public function getTranslator(): TranslatorInterface + { + return $this->translator ??= Translator::get(); + } + + /** + * Reset the format used to the default when type juggling a Carbon instance to a string + * + * @return void + */ + public function resetToStringFormat(): void + { + $this->setToStringFormat(null); + } + + /** + * Set the default format used when type juggling a Carbon instance to a string. + */ + public function setToStringFormat(string|Closure|null $format): void + { + $this->mergeSettings([ + 'toStringFormat' => $format, + ]); + } + + /** + * JSON serialize all Carbon instances using the given callback. + */ + public function serializeUsing(string|callable|null $format): void + { + $this->mergeSettings([ + 'toJsonFormat' => $format, + ]); + } + + /** + * Enable the strict mode (or disable with passing false). + */ + public function useStrictMode(bool $strictModeEnabled = true): void + { + $this->mergeSettings([ + 'strictMode' => $strictModeEnabled, + ]); + } + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + */ + public function isStrictModeEnabled(): bool + { + return $this->getSettings()['strictMode'] ?? true; + } + + /** + * Indicates if months should be calculated with overflow. + */ + public function useMonthsOverflow(bool $monthsOverflow = true): void + { + $this->mergeSettings([ + 'monthOverflow' => $monthsOverflow, + ]); + } + + /** + * Reset the month overflow behavior. + */ + public function resetMonthsOverflow(): void + { + $this->useMonthsOverflow(); + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + */ + public function shouldOverflowMonths(): bool + { + return $this->getSettings()['monthOverflow'] ?? true; + } + + /** + * Indicates if years should be calculated with overflow. + */ + public function useYearsOverflow(bool $yearsOverflow = true): void + { + $this->mergeSettings([ + 'yearOverflow' => $yearsOverflow, + ]); + } + + /** + * Reset the month overflow behavior. + */ + public function resetYearsOverflow(): void + { + $this->useYearsOverflow(); + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + */ + public function shouldOverflowYears(): bool + { + return $this->getSettings()['yearOverflow'] ?? true; + } + + /** + * Get weekend days + * + * @return array + */ + public function getWeekendDays(): array + { + return $this->weekendDays; + } + + /** + * Set weekend days + */ + public function setWeekendDays(array $days): void + { + $this->weekendDays = $days; + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + */ + public function hasFormat(string $date, string $format): bool + { + // createFromFormat() is known to handle edge cases silently. + // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. + // To ensure we're really testing against our desired format, perform an additional regex validation. + + return $this->matchFormatPattern($date, preg_quote($format, '/'), $this->regexFormats); + } + + /** + * Checks if the (date)time string is in a given format. + * + * @example + * ``` + * Carbon::hasFormatWithModifiers('31/08/2015', 'd#m#Y'); // true + * Carbon::hasFormatWithModifiers('31/08/2015', 'm#d#Y'); // false + * ``` + */ + public function hasFormatWithModifiers(string $date, string $format): bool + { + return $this->matchFormatPattern($date, $format, array_merge($this->regexFormats, $this->regexFormatModifiers)); + } + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * Only the moment is mocked with setTestNow(), the timezone will still be the one passed + * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public function setTestNow(mixed $testNow = null): void + { + $this->useTimezoneFromTestNow = false; + $this->testNow = $testNow instanceof Closure + ? $testNow + : $this->make($testNow); + } + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * - When a string containing the desired time is passed to Carbon::parse(). + * + * It will also align default timezone (e.g. call date_default_timezone_set()) with + * the second argument or if null, with the timezone of the given date object. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * /!\ Use this method for unit tests only. + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + */ + public function setTestNowAndTimezone(mixed $testNow = null, $timezone = null): void + { + if ($testNow) { + $this->testDefaultTimezone ??= date_default_timezone_get(); + } + + $useDateInstanceTimezone = $testNow instanceof DateTimeInterface; + + if ($useDateInstanceTimezone) { + $this->setDefaultTimezone($testNow->getTimezone()->getName(), $testNow); + } + + $this->setTestNow($testNow); + $this->useTimezoneFromTestNow = ($timezone === null && $testNow instanceof Closure); + + if (!$useDateInstanceTimezone) { + $now = $this->getMockedTestNow(\func_num_args() === 1 ? null : $timezone); + $this->setDefaultTimezone($now?->tzName ?? $this->testDefaultTimezone ?? 'UTC', $now); + } + + if (!$testNow) { + $this->testDefaultTimezone = null; + } + } + + /** + * Temporarily sets a static date to be used within the callback. + * Using setTestNow to set the date, executing the callback, then + * clearing the test instance. + * + * /!\ Use this method for unit tests only. + * + * @template T + * + * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance + * @param Closure(): T $callback + * + * @return T + */ + public function withTestNow(mixed $testNow, callable $callback): mixed + { + $previousTestNow = $this->getTestNow(); + $this->setTestNow($testNow); + + try { + $result = $callback(); + } finally { + $this->setTestNow($previousTestNow); + } + + return $result; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Closure|CarbonInterface|null the current instance used for testing + */ + public function getTestNow(): Closure|CarbonInterface|null + { + if ($this->testNow === null) { + $factory = FactoryImmutable::getDefaultInstance(); + + if ($factory !== $this) { + return $factory->getTestNow(); + } + } + + return $this->testNow; + } + + public function handleTestNowClosure( + Closure|CarbonInterface|null $testNow, + DateTimeZone|string|int|null $timezone = null, + ): ?CarbonInterface { + if ($testNow instanceof Closure) { + $callback = Callback::fromClosure($testNow); + $realNow = new DateTimeImmutable('now'); + $testNow = $testNow($callback->prepareParameter($this->parse( + $realNow->format('Y-m-d H:i:s.u'), + $timezone ?? $realNow->getTimezone(), + ))); + + if ($testNow !== null && !($testNow instanceof DateTimeInterface)) { + $function = $callback->getReflectionFunction(); + $type = \is_object($testNow) ? $testNow::class : \gettype($testNow); + + throw new RuntimeException( + 'The test closure defined in '.$function->getFileName(). + ' at line '.$function->getStartLine().' returned '.$type. + '; expected '.CarbonInterface::class.'|null', + ); + } + + if (!($testNow instanceof CarbonInterface)) { + $timezone ??= $this->useTimezoneFromTestNow + ? $testNow->getTimezone() + : new CarbonTimeZone(date_default_timezone_get()); + $testNow = $this->__call('instance', [$testNow, $timezone]); + } + } + + if ($testNow !== null && $timezone === null) { + if ($testNow instanceof DateTime) { + $testNow = clone $testNow; + } + + $testNow = $testNow->setTimezone(date_default_timezone_get()); + } + + return $testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return bool true if there is a test instance, otherwise false + */ + public function hasTestNow(): bool + { + return $this->getTestNow() !== null; + } + + public function withTimeZone(DateTimeZone|string|int|null $timezone): static + { + $factory = clone $this; + $factory->settings['timezone'] = $timezone; + + return $factory; + } + + public function __call(string $name, array $arguments): mixed { $method = new ReflectionMethod($this->className, $name); $settings = $this->settings; if ($settings && isset($settings['timezone'])) { - $tzParameters = array_filter($method->getParameters(), function ($parameter) { + $timezoneParameters = array_filter($method->getParameters(), function ($parameter) { return \in_array($parameter->getName(), ['tz', 'timezone'], true); }); + $timezoneSetting = $settings['timezone']; if (isset($arguments[0]) && \in_array($name, ['instance', 'make', 'create', 'parse'], true)) { if ($arguments[0] instanceof DateTimeInterface) { @@ -311,16 +759,133 @@ class Factory } elseif (\is_string($arguments[0]) && date_parse($arguments[0])['is_localtime']) { unset($settings['timezone'], $settings['innerTimezone']); } - } elseif (\count($tzParameters)) { - array_splice($arguments, key($tzParameters), 0, [$settings['timezone']]); + } + + if (\count($timezoneParameters)) { + $index = key($timezoneParameters); + + if (!isset($arguments[$index])) { + array_splice($arguments, key($timezoneParameters), 0, [$timezoneSetting]); + } + unset($settings['timezone']); } } - $result = $this->className::$name(...$arguments); + $clock = FactoryImmutable::getCurrentClock(); + FactoryImmutable::setCurrentClock($this); + + try { + $result = $this->className::$name(...$arguments); + } finally { + FactoryImmutable::setCurrentClock($clock); + } + + if (isset($this->translator)) { + $settings['translator'] = $this->translator; + } return $result instanceof CarbonInterface && !empty($settings) ? $result->settings($settings) : $result; } + + /** + * Get the mocked date passed in setTestNow() and if it's a Closure, execute it. + */ + protected function getMockedTestNow(DateTimeZone|string|int|null $timezone): ?CarbonInterface + { + $testNow = $this->handleTestNowClosure($this->getTestNow()); + + if ($testNow instanceof CarbonInterface) { + $testNow = $testNow->avoidMutation(); + + if ($timezone !== null) { + return $testNow->setTimezone($timezone); + } + } + + return $testNow; + } + + /** + * Checks if the (date)time string is in a given format with + * given list of pattern replacements. + * + * @example + * ``` + * Carbon::hasFormat('11:12:45', 'h:i:s'); // true + * Carbon::hasFormat('13:12:45', 'h:i:s'); // false + * ``` + * + * @param string $date + * @param string $format + * @param array $replacements + * + * @return bool + */ + private function matchFormatPattern(string $date, string $format, array $replacements): bool + { + // Null bytes would collide with the fragment sentinels used below. + $format = str_replace("\0", '', $format); + // Preg quote, but remove escaped backslashes since we'll deal with escaped characters in the format string. + $regex = str_replace('\\\\', '\\', $format); + // Replace not-escaped letters, wrapping each generated fragment between + // null-byte sentinels so the generic quoting below leaves them intact. + $regex = preg_replace_callback( + '/(? "\0".$match[1].strtr($match[2], $replacements)."\0", + $regex, + ); + // Replace escaped letters by the letter itself + $regex = preg_replace('/(? $chunk) { + if ($index % 2) { + continue; + } + + // Quote every character that did not come from a format token: + // backslash escapes resolve to their quoted literal target, any + // other byte (including dangling backslashes) is quoted as-is. + $chunks[$index] = preg_replace_callback( + '/\\\\.|./s', + static fn ($match) => preg_quote( + $match[0][0] === '\\' && isset($match[0][1]) ? $match[0][1] : $match[0], + '/', + ), + $chunk, + ); + } + + return (bool) @preg_match('/^'.implode('', $chunks).'$/', $date); + } + + private function setDefaultTimezone(string $timezone, ?DateTimeInterface $date = null): void + { + $previous = null; + $success = false; + + try { + $success = date_default_timezone_set($timezone); + } catch (Throwable $exception) { + $previous = $exception; + } + + if (!$success) { + $suggestion = @CarbonTimeZone::create($timezone)->toRegionName($date); + + throw new InvalidArgumentException( + "Timezone ID '$timezone' is invalid". + ($suggestion && $suggestion !== $timezone ? ", did you mean '$suggestion'?" : '.')."\n". + "It must be one of the IDs from DateTimeZone::listIdentifiers(),\n". + 'For the record, hours/minutes offset are relevant only for a particular moment, '. + 'but not as a default timezone.', + 0, + $previous + ); + } + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php index d88a1cf67..43176bd0d 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/FactoryImmutable.php @@ -1,5 +1,7 @@ * - * @method bool canBeCreatedFromFormat($date, $format) Checks if the (date)time string is in a given format and valid to create a - * new instance. - * @method CarbonImmutable|false create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) Create a new Carbon instance from a specific date and time. - * If any of $year, $month or $day are set to null their now() values will - * be used. - * If $hour is null it will be set to its now() value and the default - * values for $minute and $second will be their now() values. - * If $hour is not null then the default values for $minute and $second - * will be 0. - * @method CarbonImmutable createFromDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to now. - * @method CarbonImmutable|false createFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. - * @method CarbonImmutable|false createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). - * @method CarbonImmutable|false createFromLocaleFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific format and a string in a given language. - * @method CarbonImmutable|false createFromLocaleIsoFormat($format, $locale, $time, $tz = null) Create a Carbon instance from a specific ISO format and a string in a given language. - * @method CarbonImmutable createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) Create a Carbon instance from just a time. The date portion is set to today. - * @method CarbonImmutable createFromTimeString($time, $tz = null) Create a Carbon instance from a time string. The date portion is set to today. - * @method CarbonImmutable createFromTimestamp($timestamp, $tz = null) Create a Carbon instance from a timestamp and set the timezone (use default one if not specified). - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method CarbonImmutable createFromTimestampMs($timestamp, $tz = null) Create a Carbon instance from a timestamp in milliseconds. - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method CarbonImmutable createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method CarbonImmutable createFromTimestampUTC($timestamp) Create a Carbon instance from an timestamp keeping the timezone to UTC. - * Timestamp input can be given as int, float or a string containing one or more numbers. - * @method CarbonImmutable createMidnightDate($year = null, $month = null, $day = null, $tz = null) Create a Carbon instance from just a date. The time portion is set to midnight. - * @method CarbonImmutable|false createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) Create a new safe Carbon instance from a specific date and time. - * If any of $year, $month or $day are set to null their now() values will - * be used. - * If $hour is null it will be set to its now() value and the default - * values for $minute and $second will be their now() values. - * If $hour is not null then the default values for $minute and $second - * will be 0. - * If one of the set values is not valid, an InvalidDateException - * will be thrown. - * @method CarbonInterface createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null) Create a new Carbon instance from a specific date and time using strict validation. - * @method CarbonImmutable disableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method CarbonImmutable enableHumanDiffOption($humanDiffOption) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method mixed executeWithLocale($locale, $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, - * then return the result of the closure (or null if the closure was void). - * @method CarbonImmutable fromSerialized($value) Create an instance from a serialized string. - * @method void genericMacro($macro, $priority = 0) Register a custom macro. - * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. - * (It will ignore custom translator dynamic loading.) - * @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native - * name, region and variant of the locale. - * @method array getDays() Get the days of the week - * @method string|null getFallbackLocale() Get the fallback locale. - * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). - * @method int getHumanDiffOptions() Return default humanDiff() options (merged flags as integer). - * @method array getIsoUnits() Returns list of locale units for ISO formatting. - * @method array getLastErrors() {@inheritdoc} - * @method string getLocale() Get the current translator locale. - * @method callable|null getMacro($name) Get the raw callable macro registered globally for a given name. - * @method int getMidDayAt() get midday/noon hour - * @method Closure|CarbonImmutable getTestNow() Get the Carbon instance (real or mock) to be returned when a "now" - * instance is created. - * @method string getTimeFormatByPrecision($unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. - * @method string getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. - * @method \Symfony\Component\Translation\TranslatorInterface getTranslator() Get the default translator instance in use. - * @method int getWeekEndsAt() Get the last day of week - * @method int getWeekStartsAt() Get the first day of week - * @method array getWeekendDays() Get weekend days - * @method bool hasFormat($date, $format) Checks if the (date)time string is in a given format. - * @method bool hasFormatWithModifiers($date, $format) Checks if the (date)time string is in a given format. - * @method bool hasMacro($name) Checks if macro is registered globally. - * @method bool hasRelativeKeywords($time) Determine if a time string will produce a relative date. - * @method bool hasTestNow() Determine if there is a valid test instance set. A valid test instance - * is anything that is not null. - * @method CarbonImmutable instance($date) Create a Carbon instance from a DateTime one. - * @method bool isImmutable() Returns true if the current class/instance is immutable. - * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. - * @method bool isMutable() Returns true if the current class/instance is mutable. - * @method bool isStrictModeEnabled() Returns true if the strict mode is globally in use, false else. - * (It can be overridden in specific instances.) - * @method bool localeHasDiffOneDayWords($locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). - * Support is considered enabled if the 3 words are translated in the given locale. - * @method bool localeHasDiffSyntax($locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). - * Support is considered enabled if the 4 sentences are translated in the given locale. - * @method bool localeHasDiffTwoDayWords($locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). - * Support is considered enabled if the 2 words are translated in the given locale. - * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). - * Support is considered enabled if the 4 sentences are translated in the given locale. - * @method bool localeHasShortUnits($locale) Returns true if the given locale is internally supported and has short-units support. - * Support is considered enabled if either year, day or hour has a short variant translated. - * @method void macro($name, $macro) Register a custom macro. - * @method CarbonImmutable|null make($var) Make a Carbon instance from given variable if possible. - * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals - * and recurrences). Throw an exception for invalid format, but otherwise return null. - * @method CarbonImmutable maxValue() Create a Carbon instance for the greatest supported date. - * @method CarbonImmutable minValue() Create a Carbon instance for the lowest supported date. - * @method void mixin($mixin) Mix another object into the class. - * @method CarbonImmutable parse($time = null, $tz = null) Create a carbon instance from a string. - * This is an alias for the constructor that allows better fluent syntax - * as it allows you to do Carbon::parse('Monday next week')->fn() rather - * than (new Carbon('Monday next week'))->fn(). - * @method CarbonImmutable parseFromLocale($time, $locale = null, $tz = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). - * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). - * @method CarbonImmutable|false rawCreateFromFormat($format, $time, $tz = null) Create a Carbon instance from a specific format. - * @method CarbonImmutable rawParse($time = null, $tz = null) Create a carbon instance from a string. - * This is an alias for the constructor that allows better fluent syntax - * as it allows you to do Carbon::parse('Monday next week')->fn() rather - * than (new Carbon('Monday next week'))->fn(). - * @method CarbonImmutable resetMacros() Remove all macros and generic macros. - * @method void resetMonthsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method void resetToStringFormat() Reset the format used to the default when type juggling a Carbon instance to a string - * @method void resetYearsOverflow() @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method void serializeUsing($callback) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather transform Carbon object before the serialization. - * JSON serialize all Carbon instances using the given callback. - * @method CarbonImmutable setFallbackLocale($locale) Set the fallback locale. - * @method CarbonImmutable setHumanDiffOptions($humanDiffOptions) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method bool setLocale($locale) Set the current translator locale and indicate if the source locale file exists. - * Pass 'auto' as locale to use closest language from the current LC_TIME locale. - * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather consider mid-day is always 12pm, then if you need to test if it's an other - * hour, test it explicitly: - * $date->format('G') == 13 - * or to set explicitly to a given hour: - * $date->setTime(13, 0, 0, 0) - * Set midday/noon hour - * @method CarbonImmutable setTestNow($testNow = null) Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned - * specifically under the following conditions: - * - A call to the static now() method, ex. Carbon::now() - * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) - * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') - * - When a string containing the desired time is passed to Carbon::parse(). - * Note the timezone parameter was left out of the examples above and - * has no affect as the mock value will be returned regardless of its value. - * Only the moment is mocked with setTestNow(), the timezone will still be the one passed - * as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()). - * To clear the test instance call this method using the default - * parameter of null. - * /!\ Use this method for unit tests only. - * @method CarbonImmutable setTestNowAndTimezone($testNow = null, $tz = null) Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned - * specifically under the following conditions: - * - A call to the static now() method, ex. Carbon::now() - * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) - * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') - * - When a string containing the desired time is passed to Carbon::parse(). - * It will also align default timezone (e.g. call date_default_timezone_set()) with - * the second argument or if null, with the timezone of the given date object. - * To clear the test instance call this method using the default - * parameter of null. - * /!\ Use this method for unit tests only. - * @method void setToStringFormat($format) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and - * use other method or custom format passed to format() method if you need to dump another string - * format. - * Set the default format used when type juggling a Carbon instance to a string. - * @method void setTranslator(TranslatorInterface $translator) Set the default translator instance to use. - * @method CarbonImmutable setUtf8($utf8) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use UTF-8 language packages on every machine. - * Set if UTF8 will be used for localized date/time. - * @method void setWeekEndsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek - * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the - * start of week according to current locale selected and implicitly the end of week. - * Set the last day of week - * @method void setWeekStartsAt($day) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the - * 'first_day_of_week' locale setting to change the start of week according to current locale - * selected and implicitly the end of week. - * Set the first day of week - * @method void setWeekendDays($days) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather consider week-end is always saturday and sunday, and if you have some custom - * week-end days to handle, give to those days an other name and create a macro for them: - * ``` - * Carbon::macro('isDayOff', function ($date) { - * return $date->isSunday() || $date->isMonday(); - * }); - * Carbon::macro('isNotDayOff', function ($date) { - * return !$date->isDayOff(); - * }); - * if ($someDate->isDayOff()) ... - * if ($someDate->isNotDayOff()) ... - * // Add 5 not-off days - * $count = 5; - * while ($someDate->isDayOff() || ($count-- > 0)) { - * $someDate->addDay(); - * } - * ``` - * Set weekend days - * @method bool shouldOverflowMonths() Get the month overflow global behavior (can be overridden in specific instances). - * @method bool shouldOverflowYears() Get the month overflow global behavior (can be overridden in specific instances). - * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). - * @method CarbonImmutable today($tz = null) Create a Carbon instance for today. - * @method CarbonImmutable tomorrow($tz = null) Create a Carbon instance for tomorrow. - * @method string translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. - * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. - * @method void useMonthsOverflow($monthsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method CarbonImmutable useStrictMode($strictModeEnabled = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @method void useYearsOverflow($yearsOverflow = true) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @method mixed withTestNow($testNow, $callback) Temporarily sets a static date to be used within the callback. - * Using setTestNow to set the date, executing the callback, then - * clearing the test instance. - * /!\ Use this method for unit tests only. - * @method CarbonImmutable yesterday($tz = null) Create a Carbon instance for yesterday. + * @method bool canBeCreatedFromFormat(?string $date, string $format) Checks if the (date)time string is in a given format and valid to create a + * new instance. + * @method ?CarbonImmutable create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null) Create a new Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * @method CarbonImmutable createFromDate($year = null, $month = null, $day = null, $timezone = null) Create a Carbon instance from just a date. The time portion is set to now. + * @method ?CarbonImmutable createFromFormat($format, $time, $timezone = null) Create a Carbon instance from a specific format. + * @method ?CarbonImmutable createFromIsoFormat(string $format, string $time, $timezone = null, ?string $locale = 'en', ?TranslatorInterface $translator = null) Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). + * Era tokens (N to NNNNN, y, yy, yyy, yyyy, yo) are read with the "eras" of $locale: the era name gives the + * era and the year is the year of this era, so 'D MMMM N y' with 'th' reads '29 กุมภาพันธ์ พ.ศ. 2567' as + * 2024-02-29. Without any N token, the era of today's date in this locale is used. + * @method ?CarbonImmutable createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null) Create a Carbon instance from a specific format and a string in a given language. + * @method ?CarbonImmutable createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null) Create a Carbon instance from a specific ISO format and a string in a given language. + * @method CarbonImmutable createFromTime($hour = 0, $minute = 0, $second = 0, $timezone = null) Create a Carbon instance from just a time. The date portion is set to today. + * @method CarbonImmutable createFromTimeString(string $time, DateTimeZone|string|int|null $timezone = null) Create a Carbon instance from a time string. The date portion is set to today. + * @method CarbonImmutable createFromTimestamp(string|int|float $timestamp, DateTimeZone|string|int|null $timezone = null) Create a Carbon instance from a timestamp and set the timezone (UTC by default). + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMs(string|int|float $timestamp, DateTimeZone|string|int|null $timezone = null) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampMsUTC($timestamp) Create a Carbon instance from a timestamp in milliseconds. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createFromTimestampUTC(string|int|float $timestamp) Create a Carbon instance from a timestamp keeping the timezone to UTC. + * Timestamp input can be given as int, float or a string containing one or more numbers. + * @method CarbonImmutable createMidnightDate($year = null, $month = null, $day = null, $timezone = null) Create a Carbon instance from just a date. The time portion is set to midnight. + * @method ?CarbonImmutable createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null) Create a new safe Carbon instance from a specific date and time. + * If any of $year, $month or $day are set to null their now() values will + * be used. + * If $hour is null it will be set to its now() value and the default + * values for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * If one of the set values is not valid, an InvalidDateException + * will be thrown. + * @method CarbonImmutable createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $timezone = null) Create a new Carbon instance from a specific date and time using strict validation. + * @method mixed executeWithLocale(string $locale, callable $func) Set the current locale to the given, execute the passed function, reset the locale to previous one, + * then return the result of the closure (or null if the closure was void). + * @method CarbonImmutable fromSerialized($value, array $options = []) Create an instance from a serialized string. + * Unserializing object can lead to arbitrary execution of period filters, and assignation + * of unvalidated values to properties. + * You should never unserialize a value from not from a trusted source. Ensure you unserialize + * only strings coming from storage no unauthorized party can read nor write, prefer to exchange + * data with other systems via static formats such as JSON. + * If the data has to transit via untrusted systems before unserialization, then you should + * sign it cryptographically (with HMAC or OpenSSL for example), and verify the signature + * before unserializing it. + * If the content of $value is unknown, consider using the allowed_classes option to limit + * the types of objects that can be built, for instance: + * @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales. + * (It will ignore custom translator dynamic loading.) + * @method array getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native + * name, region and variant of the locale. + * @method array getDays() Get the days of the week. + * @method ?string getFallbackLocale() Get the fallback locale. + * @method array getFormatsToIsoReplacements() List of replacements from date() format to isoFormat(). + * @method array getIsoUnits() Returns list of locale units for ISO formatting. + * @method array|false getLastErrors() {@inheritdoc} + * @method string getLocale() Get the current translator locale. + * @method int getMidDayAt() get midday/noon hour + * @method string getTimeFormatByPrecision(string $unitPrecision) Return a format from H:i to H:i:s.u according to given unit precision. + * @method string|Closure|null getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) Returns raw translation message for a given key. + * @method int getWeekEndsAt(?string $locale = null) Get the last day of week. + * @method int getWeekStartsAt(?string $locale = null) Get the first day of week. + * @method bool hasRelativeKeywords(?string $time) Determine if a time string will produce a relative date. + * @method CarbonImmutable instance(DateTimeInterface $date) Create a Carbon instance from a DateTime one. + * @method bool isImmutable() Returns true if the current class/instance is immutable. + * @method bool isModifiableUnit($unit) Returns true if a property can be changed via setter. + * @method bool isMutable() Returns true if the current class/instance is mutable. + * @method bool localeHasDiffOneDayWords(string $locale) Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow). + * Support is considered enabled if the 3 words are translated in the given locale. + * @method bool localeHasDiffSyntax(string $locale) Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasDiffTwoDayWords(string $locale) Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow). + * Support is considered enabled if the 2 words are translated in the given locale. + * @method bool localeHasPeriodSyntax($locale) Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X). + * Support is considered enabled if the 4 sentences are translated in the given locale. + * @method bool localeHasShortUnits(string $locale) Returns true if the given locale is internally supported and has short-units support. + * Support is considered enabled if either year, day or hour has a short variant translated. + * @method ?CarbonImmutable make($var, DateTimeZone|string|null $timezone = null) Make a Carbon instance from given variable if possible. + * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals + * and recurrences). Throw an exception for invalid format, but otherwise return null. + * @method void mixin(object|string $mixin) Mix another object into the class. + * @method CarbonImmutable parse(DateTimeInterface|WeekDay|Month|string|int|float|null $time, DateTimeZone|string|int|null $timezone = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method CarbonImmutable parseFromLocale(string $time, ?string $locale = null, DateTimeZone|string|int|null $timezone = null) Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). + * @method string pluralUnit(string $unit) Returns standardized plural of a given singular/plural unit name (in English). + * @method ?CarbonImmutable rawCreateFromFormat(string $format, string $time, $timezone = null) Create a Carbon instance from a specific format. + * @method CarbonImmutable rawParse(DateTimeInterface|WeekDay|Month|string|int|float|null $time, DateTimeZone|string|int|null $timezone = null) Create a carbon instance from a string. + * This is an alias for the constructor that allows better fluent syntax + * as it allows you to do Carbon::parse('Monday next week')->fn() rather + * than (new Carbon('Monday next week'))->fn(). + * @method void setFallbackLocale(string $locale) Set the fallback locale. + * @method void setLocale(string $locale) Set the current translator locale and indicate if the source locale file exists. + * Pass 'auto' as locale to use the closest language to the current LC_TIME locale. + * @method void setMidDayAt($hour) @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather consider mid-day is always 12pm, then if you need to test if it's an other + * hour, test it explicitly: + * $date->format('G') == 13 + * or to set explicitly to a given hour: + * $date->setTime(13, 0, 0, 0) + * Set midday/noon hour + * @method string singularUnit(string $unit) Returns standardized singular of a given singular/plural unit name (in English). + * @method CarbonImmutable today(DateTimeZone|string|int|null $timezone = null) Create a Carbon instance for today. + * @method CarbonImmutable tomorrow(DateTimeZone|string|int|null $timezone = null) Create a Carbon instance for tomorrow. + * @method string translateTimeString(string $timeString, ?string $from = null, ?string $to = null, int $mode = CarbonInterface::TRANSLATE_ALL) Translate a time string from a locale to an other. + * @method string translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) Translate using translation string or callback available. + * @method CarbonImmutable yesterday(DateTimeZone|string|int|null $timezone = null) Create a Carbon instance for yesterday. * * */ class FactoryImmutable extends Factory implements ClockInterface { - protected $className = CarbonImmutable::class; + protected string $className = CarbonImmutable::class; + + private static ?self $defaultInstance = null; + + private static ?WrapperClock $currentClock = null; + + /** + * @internal Instance used for static calls, such as Carbon::getTranslator(), CarbonImmutable::setTestNow(), etc. + */ + public static function getDefaultInstance(): self + { + return self::$defaultInstance ??= new self(); + } + + /** + * @internal Instance used for static calls possibly called by non-static methods. + */ + public static function getInstance(): Factory + { + return self::$currentClock?->getFactory() ?? self::getDefaultInstance(); + } + + /** + * @internal Set instance before creating new dates. + */ + public static function setCurrentClock(ClockInterface|Factory|DateTimeInterface|null $currentClock): void + { + if ($currentClock && !($currentClock instanceof WrapperClock)) { + $currentClock = new WrapperClock($currentClock); + } + + self::$currentClock = $currentClock; + } + + /** + * @internal Instance used to link new object to their factory creator. + */ + public static function getCurrentClock(): ?WrapperClock + { + return self::$currentClock; + } /** * Get a Carbon instance for the current date and time. - * - * @param DateTimeZone|string|int|null $tz - * - * @return CarbonImmutable */ - public function now($tz = null): DateTimeImmutable + public function now(DateTimeZone|string|int|null $timezone = null): CarbonImmutable { - $className = $this->className; + return $this->__call('now', [$timezone]); + } - return new $className(null, $tz); + public function sleep(int|float $seconds): void + { + if ($this->hasTestNow()) { + $this->setTestNow($this->getTestNow()->avoidMutation()->addSeconds($seconds)); + + return; + } + + (new NativeClock('UTC'))->sleep($seconds); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php index e55e591b5..b6f7d0b3a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/aa_ET.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Acaada', 'Etleeni', 'Talaata', 'Arbaqa', 'Kamiisi', 'Gumqata', 'Sabti'], 'weekdays_short' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], 'weekdays_min' => ['Aca', 'Etl', 'Tal', 'Arb', 'Kam', 'Gum', 'Sab'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['saaku', 'carra'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php index 27771d7aa..87592fe1b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/af.php @@ -64,9 +64,7 @@ return [ 'lastWeek' => '[Laas] dddd [om] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { - return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); - }, + 'ordinal' => static fn ($number) => $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'), 'meridiem' => ['VM', 'NM'], 'months' => ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], 'months_short' => ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php index ece80621a..7cc676b37 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/am_ET.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['እሑድ', 'ሰኞ', 'ማክሰኞ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], 'weekdays_short' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], 'weekdays_min' => ['እሑድ', 'ሰኞ ', 'ማክሰ', 'ረቡዕ', 'ሐሙስ', 'ዓርብ', 'ቅዳሜ'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ጡዋት', 'ከሰዓት'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php index 11069be3f..00baa980a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/anp_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php index 5f73f6395..8454cb079 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar.php @@ -16,6 +16,7 @@ * - MLTDev * - Mohamed Sabil (mohamedsabil83) * - Yazan Alnugnugh (yazan-alnugnugh) + * - Mousab Salah (123mousab) */ $months = [ 'يناير', @@ -33,20 +34,20 @@ $months = [ ]; return [ - 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), - 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => ':time من الآن', 'after' => 'بعد :time', @@ -57,10 +58,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+عند)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php index 35a22b1d2..35180965a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_AE.php @@ -20,7 +20,7 @@ return array_replace_recursive(require __DIR__.'/ar.php', [ ], 'months' => ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'], 'months_short' => ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'], - 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت '], + 'weekdays' => ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'], 'weekdays_short' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], 'weekdays_min' => ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], 'first_day_of_week' => 6, diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php index aea4eeecc..297a68736 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_DZ.php @@ -16,6 +16,7 @@ * - JD Isaacks * - Atef Ben Ali (atefBB) * - Mohamed Sabil (mohamedsabil83) + * - Mousab Salah (123mousab) */ $months = [ 'جانفي', @@ -33,20 +34,20 @@ $months = [ ]; return [ - 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), - 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => 'في :time', 'after' => 'بعد :time', @@ -57,10 +58,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+على)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php index b3fb1cfec..2e1dd5f33 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_KW.php @@ -17,37 +17,38 @@ * - Atef Ben Ali (atefBB) * - Mohamed Sabil (mohamedsabil83) * - Abdullah-Alhariri + * - Mousab Salah (123mousab) */ $months = [ 'يناير', 'فبراير', 'مارس', 'أبريل', - 'ماي', + 'مايو', 'يونيو', - 'يوليوز', - 'غشت', - 'شتنبر', + 'يوليو', + 'أغسطس', + 'سبتمبر', 'أكتوبر', - 'نونبر', - 'دجنبر', + 'نوفمبر', + 'ديسمبر', ]; return [ - 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), - 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => 'في :time', 'after' => 'بعد :time', @@ -58,10 +59,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+على)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php index 1f0af49d6..85c7fb115 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_LY.php @@ -14,6 +14,7 @@ * - Atef Ben Ali (atefBB) * - Ibrahim AshShohail * - MLTDev + * - Mousab Salah (123mousab) */ $months = [ @@ -32,20 +33,20 @@ $months = [ ]; return [ - 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), - 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة']), - 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), - 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر']), - 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), - 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع']), - 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), - 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم']), - 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), - 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة']), - 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), - 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة']), - 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), - 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => ':time من الآن', 'after' => 'بعد :time', @@ -56,10 +57,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+عند)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['مرة', 'مرة', ':count مرتين', ':count مرات', ':count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php index 047ae05af..2ce193af2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_MA.php @@ -15,6 +15,7 @@ * - JD Isaacks * - Atef Ben Ali (atefBB) * - Mohamed Sabil (mohamedsabil83) + * - Mousab Salah (123mousab) */ $months = [ 'يناير', @@ -32,20 +33,20 @@ $months = [ ]; return [ - 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), - 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => 'في :time', 'after' => 'بعد :time', @@ -56,10 +57,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+على)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php index 550b0c73a..44d396e9f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_SA.php @@ -16,6 +16,7 @@ * - Atef Ben Ali (atefBB) * - Mohamed Sabil (mohamedsabil83) * - Abdullah-Alhariri + * - Mousab Salah (123mousab) */ $months = [ 'يناير', @@ -33,20 +34,20 @@ $months = [ ]; return [ - 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), - 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => 'في :time', 'after' => 'بعد :time', @@ -57,10 +58,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+على)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php index c2d4b43d0..b5d2b9c2a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_Shakl.php @@ -14,6 +14,7 @@ * - Abdellah Chadidi * - Atef Ben Ali (atefBB) * - Mohamed Sabil (mohamedsabil83) + * - Mousab Salah (123mousab) */ // Same for long and short $months = [ @@ -33,20 +34,20 @@ $months = [ ]; return [ - 'year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), - 'a_year' => implode('|', ['{0}:count سَنَة', '{1}سَنَة', '{2}سَنَتَيْن', ']2,11[:count سَنَوَات', ']10,Inf[:count سَنَة']), - 'month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), - 'a_month' => implode('|', ['{0}:count شَهْرَ', '{1}شَهْرَ', '{2}شَهْرَيْن', ']2,11[:count أَشْهُر', ']10,Inf[:count شَهْرَ']), - 'week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), - 'a_week' => implode('|', ['{0}:count أُسْبُوع', '{1}أُسْبُوع', '{2}أُسْبُوعَيْن', ']2,11[:count أَسَابِيع', ']10,Inf[:count أُسْبُوع']), - 'day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), - 'a_day' => implode('|', ['{0}:count يَوْم', '{1}يَوْم', '{2}يَوْمَيْن', ']2,11[:count أَيَّام', ']10,Inf[:count يَوْم']), - 'hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), - 'a_hour' => implode('|', ['{0}:count سَاعَة', '{1}سَاعَة', '{2}سَاعَتَيْن', ']2,11[:count سَاعَات', ']10,Inf[:count سَاعَة']), - 'minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), - 'a_minute' => implode('|', ['{0}:count دَقِيقَة', '{1}دَقِيقَة', '{2}دَقِيقَتَيْن', ']2,11[:count دَقَائِق', ']10,Inf[:count دَقِيقَة']), - 'second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), - 'a_second' => implode('|', ['{0}:count ثَانِيَة', '{1}ثَانِيَة', '{2}ثَانِيَتَيْن', ']2,11[:count ثَوَان', ']10,Inf[:count ثَانِيَة']), + 'year' => implode('|', [':count سَنَة', 'سَنَة', 'سَنَتَيْن', ':count سَنَوَات', ':count سَنَة', ':count سَنَة']), + 'a_year' => implode('|', [':count سَنَة', 'سَنَة', 'سَنَتَيْن', ':count سَنَوَات', ':count سَنَة', ':count سَنَة']), + 'month' => implode('|', [':count شَهْرَ', 'شَهْرَ', 'شَهْرَيْن', ':count أَشْهُر', ':count شَهْرَ', ':count شَهْرَ']), + 'a_month' => implode('|', [':count شَهْرَ', 'شَهْرَ', 'شَهْرَيْن', ':count أَشْهُر', ':count شَهْرَ', ':count شَهْرَ']), + 'week' => implode('|', [':count أُسْبُوع', 'أُسْبُوع', 'أُسْبُوعَيْن', ':count أَسَابِيع', ':count أُسْبُوع', ':count أُسْبُوع']), + 'a_week' => implode('|', [':count أُسْبُوع', 'أُسْبُوع', 'أُسْبُوعَيْن', ':count أَسَابِيع', ':count أُسْبُوع', ':count أُسْبُوع']), + 'day' => implode('|', [':count يَوْم', 'يَوْم', 'يَوْمَيْن', ':count أَيَّام', ':count يَوْم', ':count يَوْم']), + 'a_day' => implode('|', [':count يَوْم', 'يَوْم', 'يَوْمَيْن', ':count أَيَّام', ':count يَوْم', ':count يَوْم']), + 'hour' => implode('|', [':count سَاعَة', 'سَاعَة', 'سَاعَتَيْن', ':count سَاعَات', ':count سَاعَة', ':count سَاعَة']), + 'a_hour' => implode('|', [':count سَاعَة', 'سَاعَة', 'سَاعَتَيْن', ':count سَاعَات', ':count سَاعَة', ':count سَاعَة']), + 'minute' => implode('|', [':count دَقِيقَة', 'دَقِيقَة', 'دَقِيقَتَيْن', ':count دَقَائِق', ':count دَقِيقَة', ':count دَقِيقَة']), + 'a_minute' => implode('|', [':count دَقِيقَة', 'دَقِيقَة', 'دَقِيقَتَيْن', ':count دَقَائِق', ':count دَقِيقَة', ':count دَقِيقَة']), + 'second' => implode('|', [':count ثَانِيَة', 'ثَانِيَة', 'ثَانِيَتَيْن', ':count ثَوَان', ':count ثَانِيَة', ':count ثَانِيَة']), + 'a_second' => implode('|', [':count ثَانِيَة', 'ثَانِيَة', 'ثَانِيَتَيْن', ':count ثَوَان', ':count ثَانِيَة', ':count ثَانِيَة']), 'ago' => 'مُنْذُ :time', 'from_now' => 'مِنَ الْآن :time', 'after' => 'بَعْدَ :time', @@ -59,10 +60,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+عند)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدًا(?:\\s+عند)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+عند)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php index f096678f4..5d805223c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ar_TN.php @@ -14,6 +14,7 @@ * - JD Isaacks * - Atef Ben Ali (atefBB) * - Mohamed Sabil (mohamedsabil83) + * - Mousab Salah (123mousab) */ $months = [ 'جانفي', @@ -31,20 +32,20 @@ $months = [ ]; return [ - 'year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'a_year' => implode('|', ['{0}:count سنة', '{1}سنة', '{2}سنتين', ']2,11[:count سنوات', ']10,Inf[:count سنة']), - 'month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'a_month' => implode('|', ['{0}:count شهر', '{1}شهر', '{2}شهرين', ']2,11[:count أشهر', ']10,Inf[:count شهر']), - 'week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'a_week' => implode('|', ['{0}:count أسبوع', '{1}أسبوع', '{2}أسبوعين', ']2,11[:count أسابيع', ']10,Inf[:count أسبوع']), - 'day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'a_day' => implode('|', ['{0}:count يوم', '{1}يوم', '{2}يومين', ']2,11[:count أيام', ']10,Inf[:count يوم']), - 'hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'a_hour' => implode('|', ['{0}:count ساعة', '{1}ساعة', '{2}ساعتين', ']2,11[:count ساعات', ']10,Inf[:count ساعة']), - 'minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'a_minute' => implode('|', ['{0}:count دقيقة', '{1}دقيقة', '{2}دقيقتين', ']2,11[:count دقائق', ']10,Inf[:count دقيقة']), - 'second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), - 'a_second' => implode('|', ['{0}:count ثانية', '{1}ثانية', '{2}ثانيتين', ']2,11[:count ثواني', ']10,Inf[:count ثانية']), + 'year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'a_year' => implode('|', [':count سنة', 'سنة', 'سنتين', ':count سنوات', ':count سنة', ':count سنة']), + 'month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'a_month' => implode('|', [':count شهر', 'شهر', 'شهرين', ':count أشهر', ':count شهر', ':count شهر']), + 'week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'a_week' => implode('|', [':count أسبوع', 'أسبوع', 'أسبوعين', ':count أسابيع', ':count أسبوع', ':count أسبوع']), + 'day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'a_day' => implode('|', [':count يوم', 'يوم', 'يومين', ':count أيام', ':count يوم', ':count يوم']), + 'hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'a_hour' => implode('|', [':count ساعة', 'ساعة', 'ساعتين', ':count ساعات', ':count ساعة', ':count ساعة']), + 'minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'a_minute' => implode('|', [':count دقيقة', 'دقيقة', 'دقيقتين', ':count دقائق', ':count دقيقة', ':count دقيقة']), + 'second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), + 'a_second' => implode('|', [':count ثانية', 'ثانية', 'ثانيتين', ':count ثواني', ':count ثانية', ':count ثانية']), 'ago' => 'منذ :time', 'from_now' => 'في :time', 'after' => 'بعد :time', @@ -55,10 +56,10 @@ return [ 'diff_yesterday' => 'أمس', 'diff_yesterday_regexp' => 'أمس(?:\\s+على)?(?:\\s+الساعة)?', 'diff_tomorrow' => 'غداً', - 'diff_tomorrow_regexp' => 'غدا(?:\\s+على)?(?:\\s+الساعة)?', + 'diff_tomorrow_regexp' => 'غدً?اً?(?:\\s+على)?(?:\\s+الساعة)?', 'diff_before_yesterday' => 'قبل الأمس', 'diff_after_tomorrow' => 'بعد غد', - 'period_recurrences' => implode('|', ['{0}مرة', '{1}مرة', '{2}:count مرتين', ']2,11[:count مرات', ']10,Inf[:count مرة']), + 'period_recurrences' => implode('|', [':count مرة', 'مرة', 'مرتين', ':count مرات', ':count مرة', ':count مرة']), 'period_interval' => 'كل :interval', 'period_start_date' => 'من :date', 'period_end_date' => 'إلى :date', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php index 5fbc3dba3..f2499abf2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/as_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['দেওবাৰ', 'সোমবাৰ', 'মঙ্গলবাৰ', 'বুধবাৰ', 'বৃহষ্পতিবাৰ', 'শুক্ৰবাৰ', 'শনিবাৰ'], 'weekdays_short' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], 'weekdays_min' => ['দেও', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহষ্পতি', 'শুক্ৰ', 'শনি'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['পূৰ্ব্বাহ্ন', 'অপৰাহ্ন'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php index ff18504f0..22374e007 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ayc_PE.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['tuminku', 'lunisa', 'martisa', 'mirkulisa', 'juywisa', 'wirnisa', 'sawäru'], 'weekdays_short' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], 'weekdays_min' => ['tum', 'lun', 'mar', 'mir', 'juy', 'wir', 'saw'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['VM', 'NM'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php index 1e92106df..5b5e3dd31 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az.php @@ -18,29 +18,30 @@ * - Orxan * - Şəhriyar İmanov * - Baran Şengül + * - Novruz Rahimov */ return [ 'year' => ':count il', - 'a_year' => '{1}bir il|]1,Inf[:count il', + 'a_year' => '{1}bir il|[-Inf,Inf]:count il', 'y' => ':count il', 'month' => ':count ay', - 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'a_month' => '{1}bir ay|[-Inf,Inf]:count ay', 'm' => ':count ay', 'week' => ':count həftə', - 'a_week' => '{1}bir həftə|]1,Inf[:count həftə', + 'a_week' => '{1}bir həftə|[-Inf,Inf]:count həftə', 'w' => ':count h.', 'day' => ':count gün', - 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'a_day' => '{1}bir gün|[-Inf,Inf]:count gün', 'd' => ':count g.', 'hour' => ':count saat', - 'a_hour' => '{1}bir saat|]1,Inf[:count saat', - 'h' => ':count saat', - 'minute' => ':count d.', - 'a_minute' => '{1}bir dəqiqə|]1,Inf[:count dəqiqə', - 'min' => ':count dəqiqə', - 'second' => ':count san.', - 'a_second' => '{1}birneçə saniyə|]1,Inf[:count saniyə', - 's' => ':count saniyə', + 'a_hour' => '{1}bir saat|[-Inf,Inf]:count saat', + 'h' => ':count s.', + 'minute' => ':count dəqiqə', + 'a_minute' => '{1}bir dəqiqə|[-Inf,Inf]:count dəqiqə', + 'min' => ':count d.', + 'second' => ':count saniyə', + 'a_second' => '{1}birneçə saniyə|[-Inf,Inf]:count saniyə', + 's' => ':count san.', 'ago' => ':time əvvəl', 'from_now' => ':time sonra', 'after' => ':time sonra', @@ -73,7 +74,7 @@ return [ 'lastWeek' => '[keçən həftə] dddd [saat] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { if ($number === 0) { // special case for zero return "$number-ıncı"; } @@ -103,7 +104,7 @@ return [ return $number.($suffixes[$lastDigit] ?? $suffixes[$number % 100 - $lastDigit] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'gecə'; } @@ -118,7 +119,6 @@ return [ }, 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], - 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], 'weekdays' => ['bazar', 'bazar ertəsi', 'çərşənbə axşamı', 'çərşənbə', 'cümə axşamı', 'cümə', 'şənbə'], 'weekdays_short' => ['baz', 'bze', 'çax', 'çər', 'cax', 'cüm', 'şən'], 'weekdays_min' => ['bz', 'be', 'ça', 'çə', 'ca', 'cü', 'şə'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Arab.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Arab.php new file mode 100644 index 000000000..991a0efb2 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Arab.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * Authors: + * - Mousa Moradi mousamk@gmail.com + */ +return array_replace_recursive(require __DIR__.'/en.php', [ + 'formats' => [ + 'L' => 'OY/OM/OD', + ], + 'months' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'months_short' => ['ژانویه', 'فوریه', 'مارس', 'آوریل', 'مئی', 'ژوئن', 'جولای', 'آقۇست', 'سپتامبر', 'اوْکتوْبر', 'نوْوامبر', 'دسامبر'], + 'weekdays' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_short' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'weekdays_min' => ['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چارشنبه', 'جۆمعه آخشامی', 'جۆمعه', 'شنبه'], + 'first_day_of_week' => 6, + 'alt_numbers' => ['۰۰', '۰۱', '۰۲', '۰۳', '۰۴', '۰۵', '۰۶', '۰۷', '۰۸', '۰۹', '۱۰', '۱۱', '۱۲', '۱۳', '۱۴', '۱۵', '۱۶', '۱۷', '۱۸', '۱۹', '۲۰', '۲۱', '۲۲', '۲۳', '۲۴', '۲۵', '۲۶', '۲۷', '۲۸', '۲۹', '۳۰', '۳۱', '۳۲', '۳۳', '۳۴', '۳۵', '۳۶', '۳۷', '۳۸', '۳۹', '۴۰', '۴۱', '۴۲', '۴۳', '۴۴', '۴۵', '۴۶', '۴۷', '۴۸', '۴۹', '۵۰', '۵۱', '۵۲', '۵۳', '۵۴', '۵۵', '۵۶', '۵۷', '۵۸', '۵۹', '۶۰', '۶۱', '۶۲', '۶۳', '۶۴', '۶۵', '۶۶', '۶۷', '۶۸', '۶۹', '۷۰', '۷۱', '۷۲', '۷۳', '۷۴', '۷۵', '۷۶', '۷۷', '۷۸', '۷۹', '۸۰', '۸۱', '۸۲', '۸۳', '۸۴', '۸۵', '۸۶', '۸۷', '۸۸', '۸۹', '۹۰', '۹۱', '۹۲', '۹۳', '۹۴', '۹۵', '۹۶', '۹۷', '۹۸', '۹۹'], +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php index 28fc62fe2..1bf2b587c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Cyrl.php @@ -15,6 +15,5 @@ return array_replace_recursive(require __DIR__.'/az.php', [ 'weekdays_min' => ['Б.', 'Б.Е.', 'Ч.А.', 'Ч.', 'Ҹ.А.', 'Ҹ.', 'Ш.'], 'months' => ['јанвар', 'феврал', 'март', 'апрел', 'май', 'ијун', 'ијул', 'август', 'сентјабр', 'октјабр', 'нојабр', 'декабр'], 'months_short' => ['јан', 'фев', 'мар', 'апр', 'май', 'ијн', 'ијл', 'авг', 'сен', 'окт', 'ној', 'дек'], - 'months_standalone' => ['Јанвар', 'Феврал', 'Март', 'Апрел', 'Май', 'Ијун', 'Ијул', 'Август', 'Сентјабр', 'Октјабр', 'Нојабр', 'Декабр'], 'meridiem' => ['а', 'п'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php index 991a0efb2..3fae66d32 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_IR.php @@ -9,10 +9,6 @@ * file that was distributed with this source code. */ -/* - * Authors: - * - Mousa Moradi mousamk@gmail.com - */ return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ 'L' => 'OY/OM/OD', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php index 0be33914f..7e43e01b5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/az_Latn.php @@ -16,7 +16,6 @@ return array_replace_recursive(require __DIR__.'/az.php', [ 'weekdays_min' => ['B.', 'B.E.', 'Ç.A.', 'Ç.', 'C.A.', 'C.', 'Ş.'], 'months' => ['yanvar', 'fevral', 'mart', 'aprel', 'may', 'iyun', 'iyul', 'avqust', 'sentyabr', 'oktyabr', 'noyabr', 'dekabr'], 'months_short' => ['yan', 'fev', 'mar', 'apr', 'may', 'iyn', 'iyl', 'avq', 'sen', 'okt', 'noy', 'dek'], - 'months_standalone' => ['Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul', 'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr'], 'first_day_of_week' => 1, 'formats' => [ 'LT' => 'HH:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php index ee736365a..06f4043dc 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/be.php @@ -117,39 +117,27 @@ return [ 'nextDay' => '[Заўтра ў] LT', 'nextWeek' => '[У] dddd [ў] LT', 'lastDay' => '[Учора ў] LT', - 'lastWeek' => function (CarbonInterface $current) { - switch ($current->dayOfWeek) { - case 1: - case 2: - case 4: - return '[У мінулы] dddd [ў] LT'; - default: - return '[У мінулую] dddd [ў] LT'; - } + 'lastWeek' => static fn (CarbonInterface $current) => match ($current->dayOfWeek) { + 1, 2, 4 => '[У мінулы] dddd [ў] LT', + default => '[У мінулую] dddd [ў] LT', }, 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'M': - case 'd': - case 'DDD': - case 'w': - case 'W': - return ($number % 10 === 2 || $number % 10 === 3) && ($number % 100 !== 12 && $number % 100 !== 13) ? $number.'-і' : $number.'-ы'; - case 'D': - return $number.'-га'; - default: - return $number; - } + 'ordinal' => static fn ($number, $period) => match ($period) { + 'M', 'd', 'DDD', 'w', 'W' => ($number % 10 === 2 || $number % 10 === 3) && + ($number % 100 !== 12 && $number % 100 !== 13) ? $number.'-і' : $number.'-ы', + 'D' => $number.'-га', + default => $number, }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'ночы'; } + if ($hour < 12) { return 'раніцы'; } + if ($hour < 17) { return 'дня'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php index f76807403..c793ab7c5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bg.php @@ -66,19 +66,13 @@ return [ 'nextDay' => '[Утре в] LT', 'nextWeek' => 'dddd [в] LT', 'lastDay' => '[Вчера в] LT', - 'lastWeek' => function (CarbonInterface $current) { - switch ($current->dayOfWeek) { - case 0: - case 3: - case 6: - return '[В изминалата] dddd [в] LT'; - default: - return '[В изминалия] dddd [в] LT'; - } + 'lastWeek' => static fn (CarbonInterface $current) => match ($current->dayOfWeek) { + 0, 3, 6 => '[В изминалата] dddd [в] LT', + default => '[В изминалия] dddd [в] LT', }, 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { $lastDigit = $number % 10; $last2Digits = $number % 100; if ($number === 0) { diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php index ab557cbf3..fadbf77fb 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bhb_IN.php @@ -22,5 +22,6 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 'weekdays_min' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php index bc54f3631..2f4cdfa02 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bho_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['रविवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार'], 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'गुरु', 'शुक्र', 'शनि'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php index 1fe777056..15d335c89 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bi_VU.php @@ -14,6 +14,7 @@ * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com & maninder1.s@samsung.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'dddd DD MMM YYYY', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php index 8e147899b..63c5b527b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bn.php @@ -9,6 +9,8 @@ * file that was distributed with this source code. */ +$bengaliNumbers = ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯']; + /* * Authors: * - Josh Soref @@ -18,6 +20,7 @@ * - JD Isaacks * - Saiful Islam * - Faisal Islam + * - Hayatunnabi Nabil */ return [ 'year' => ':count বছর', @@ -41,6 +44,12 @@ return [ 'second' => ':count সেকেন্ড', 'a_second' => 'কয়েক সেকেন্ড|:count সেকেন্ড', 's' => '১ সেকেন্ড|:count সেকেন্ড', + 'millisecond' => ':count মিলিসেকেন্ড', + 'a_millisecond' => 'এক মিলিসেকেন্ড|:count মিলিসেকেন্ড', + 'ms' => '১ মিলিসেকেন্ড|:count মিলিসেকেন্ড', + 'microsecond' => ':count মাইক্রোসেকেন্ড', + 'a_microsecond' => 'এক মাইক্রোসেকেন্ড|:count মাইক্রোসেকেন্ড', + 'µs' => '১ মাইক্রোসেকেন্ড|:count মাইক্রোসেকেন্ড', 'ago' => ':time আগে', 'from_now' => ':time পরে', 'after' => ':time পরে', @@ -49,6 +58,8 @@ return [ 'diff_today' => 'আজ', 'diff_yesterday' => 'গতকাল', 'diff_tomorrow' => 'আগামীকাল', + 'diff_before_yesterday' => 'গত পরশু', + 'diff_after_tomorrow' => 'আগামী পরশু', 'period_recurrences' => ':count বার|:count বার', 'period_interval' => 'প্রতি :interval', 'period_start_date' => ':date থেকে', @@ -69,7 +80,7 @@ return [ 'lastWeek' => '[গত] dddd, LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'রাত'; } @@ -90,11 +101,53 @@ return [ 'weekdays' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার'], 'weekdays_short' => ['রবি', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহস্পতি', 'শুক্র', 'শনি'], 'weekdays_min' => ['রবি', 'সোম', 'মঙ্গ', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি'], + 'ordinal' => static function ($number) use ($bengaliNumbers) { + // Convert to Bengali numerals + $bengaliNumber = str_replace( + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + $bengaliNumbers, + (string) $number + ); + + // Apply Bengali ordinal rules + $lastDigit = $number % 10; + $lastTwoDigits = $number % 100; + + // Special cases for teens (11-19) always use তম + if ($lastTwoDigits >= 11 && $lastTwoDigits <= 19) { + return $bengaliNumber.'তম'; + } + + // For numbers 1-10, use specific rules + if ($number <= 10) { + switch ($number) { + case 1: + case 5: + case 7: + case 8: + case 9: + case 10: + return $bengaliNumber.'ম'; + case 2: + case 3: + return $bengaliNumber.'য়'; + case 4: + return $bengaliNumber.'র্থ'; + case 6: + return $bengaliNumber.'ষ্ঠ'; + default: + return $bengaliNumber.'তম'; + } + } + + // For numbers > 20, all use তম + return $bengaliNumber.'তম'; + }, 'list' => [', ', ' এবং '], 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'weekdays_standalone' => ['রবিবার', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহষ্পতিবার', 'শুক্রবার', 'শনিবার'], 'weekdays_min_standalone' => ['রঃ', 'সোঃ', 'মঃ', 'বুঃ', 'বৃঃ', 'শুঃ', 'শনি'], 'months_short_standalone' => ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর'], - 'alt_numbers' => ['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], + 'alt_numbers' => $bengaliNumbers, ]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php index 99e1bf4c9..21cc90d1d 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bo.php @@ -15,13 +15,20 @@ * - JD Isaacks */ return [ - 'year' => '{1}ལོ་གཅིག|]1,Inf[:count ལོ', - 'month' => '{1}ཟླ་བ་གཅིག|]1,Inf[:count ཟླ་བ', - 'week' => ':count བདུན་ཕྲག', - 'day' => '{1}ཉིན་གཅིག|]1,Inf[:count ཉིན་', - 'hour' => '{1}ཆུ་ཚོད་གཅིག|]1,Inf[:count ཆུ་ཚོད', - 'minute' => '{1}སྐར་མ་གཅིག|]1,Inf[:count སྐར་མ', - 'second' => '{1}ལམ་སང|]1,Inf[:count སྐར་ཆ།', + 'year' => 'ལོ:count', + 'a_year' => '{1}ལོ་གཅིག|[-Inf,Inf]ལོ:count', + 'month' => 'ཟླ་བ:count', + 'a_month' => '{1}ཟླ་བ་གཅིག|[-Inf,Inf]ཟླ་བ:count', + 'week' => 'གཟའ་འཁོར་:count', + 'a_week' => 'གཟའ་འཁོར་གཅིག', + 'day' => 'ཉིན:count་', + 'a_day' => '{1}ཉིན་གཅིག|[-Inf,Inf]ཉིན:count', + 'hour' => 'ཆུ་ཚོད:count', + 'a_hour' => '{1}ཆུ་ཚོད་གཅིག|[-Inf,Inf]ཆུ་ཚོད:count', + 'minute' => 'སྐར་མ་:count', + 'a_minute' => '{1}སྐར་མ་གཅིག|[-Inf,Inf]སྐར་མ་:count', + 'second' => 'སྐར་ཆ:count', + 'a_second' => '{01}ལམ་སང|[-Inf,Inf]སྐར་ཆ:count', 'ago' => ':time སྔན་ལ', 'from_now' => ':time ལ་', 'diff_yesterday' => 'ཁ་སང', @@ -43,7 +50,7 @@ return [ 'lastWeek' => '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'མཚན་མོ'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php index 583472fb0..cdc0545b1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/br.php @@ -55,9 +55,7 @@ return [ 'lastWeek' => 'dddd [paset da] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { - return $number.($number === 1 ? 'añ' : 'vet'); - }, + 'ordinal' => static fn ($number) => $number.($number === 1 ? 'añ' : 'vet'), 'months' => ['Genver', 'C\'hwevrer', 'Meurzh', 'Ebrel', 'Mae', 'Mezheven', 'Gouere', 'Eost', 'Gwengolo', 'Here', 'Du', 'Kerzu'], 'months_short' => ['Gen', 'C\'hwe', 'Meu', 'Ebr', 'Mae', 'Eve', 'Gou', 'Eos', 'Gwe', 'Her', 'Du', 'Ker'], 'weekdays' => ['Sul', 'Lun', 'Meurzh', 'Merc\'her', 'Yaou', 'Gwener', 'Sadorn'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php index 2d80ced20..e678aa966 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/brx_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['रबिबार', 'सोबार', 'मंगलबार', 'बुदबार', 'बिसथिबार', 'सुखुरबार', 'सुनिबार'], 'weekdays_short' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], 'weekdays_min' => ['रबि', 'सम', 'मंगल', 'बुद', 'बिसथि', 'सुखुर', 'सुनि'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['फुं.', 'बेलासे.'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php index e5d68083e..52b6d12aa 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/bs.php @@ -17,6 +17,7 @@ * - shaishavgandhi05 * - Serhan Apaydın * - JD Isaacks + * - Ademir Šehić */ use Carbon\CarbonInterface; @@ -26,8 +27,8 @@ return [ 'y' => ':count godina|:count godine|:count godina', 'month' => ':count mjesec|:count mjeseca|:count mjeseci', 'm' => ':count mjesec|:count mjeseca|:count mjeseci', - 'week' => ':count sedmice|:count sedmicu|:count sedmica', - 'w' => ':count sedmice|:count sedmicu|:count sedmica', + 'week' => ':count sedmica|:count sedmice|:count sedmica', + 'w' => ':count sedmica|:count sedmice|:count sedmica', 'day' => ':count dan|:count dana|:count dana', 'd' => ':count dan|:count dana|:count dana', 'hour' => ':count sat|:count sata|:count sati', @@ -36,10 +37,17 @@ return [ 'min' => ':count minut|:count minuta|:count minuta', 'second' => ':count sekund|:count sekunda|:count sekundi', 's' => ':count sekund|:count sekunda|:count sekundi', + 'ago' => 'prije :time', 'from_now' => 'za :time', 'after' => 'nakon :time', 'before' => ':time ranije', + + 'year_ago' => ':count godinu|:count godine|:count godina', + 'year_from_now' => ':count godinu|:count godine|:count godina', + 'week_ago' => ':count sedmicu|:count sedmice|:count sedmica', + 'week_from_now' => ':count sedmicu|:count sedmice|:count sedmica', + 'diff_now' => 'sada', 'diff_today' => 'danas', 'diff_today_regexp' => 'danas(?:\\s+u)?', @@ -58,29 +66,17 @@ return [ 'calendar' => [ 'sameDay' => '[danas u] LT', 'nextDay' => '[sutra u] LT', - 'nextWeek' => function (CarbonInterface $current) { - switch ($current->dayOfWeek) { - case 0: - return '[u] [nedjelju] [u] LT'; - case 3: - return '[u] [srijedu] [u] LT'; - case 6: - return '[u] [subotu] [u] LT'; - default: - return '[u] dddd [u] LT'; - } + 'nextWeek' => static fn (CarbonInterface $current) => match ($current->dayOfWeek) { + 0 => '[u] [nedjelju] [u] LT', + 3 => '[u] [srijedu] [u] LT', + 6 => '[u] [subotu] [u] LT', + default => '[u] dddd [u] LT', }, 'lastDay' => '[jučer u] LT', - 'lastWeek' => function (CarbonInterface $current) { - switch ($current->dayOfWeek) { - case 0: - case 3: - return '[prošlu] dddd [u] LT'; - case 6: - return '[prošle] [subote] [u] LT'; - default: - return '[prošli] dddd [u] LT'; - } + 'lastWeek' => static fn (CarbonInterface $current) => match ($current->dayOfWeek) { + 0, 3 => '[prošlu] dddd [u] LT', + 6 => '[prošle] [subote] [u] LT', + default => '[prošli] dddd [u] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php index b8b19946b..824c9ce7a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ca.php @@ -72,24 +72,24 @@ return [ 'LLLL' => 'dddd D MMMM [de] YYYY [a les] H:mm', ], 'calendar' => [ - 'sameDay' => function (CarbonInterface $current) { + 'sameDay' => static function (CarbonInterface $current) { return '[avui a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; }, - 'nextDay' => function (CarbonInterface $current) { + 'nextDay' => static function (CarbonInterface $current) { return '[demà a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; }, - 'nextWeek' => function (CarbonInterface $current) { + 'nextWeek' => static function (CarbonInterface $current) { return 'dddd [a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; }, - 'lastDay' => function (CarbonInterface $current) { + 'lastDay' => static function (CarbonInterface $current) { return '[ahir a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; }, - 'lastWeek' => function (CarbonInterface $current) { + 'lastWeek' => static function (CarbonInterface $current) { return '[el] dddd [passat a '.($current->hour !== 1 ? 'les' : 'la').'] LT'; }, 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { + 'ordinal' => static function ($number, $period) { return $number.( ($period === 'w' || $period === 'W') ? 'a' : ( ($number === 1) ? 'r' : ( diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php index 99c1dcacd..b536d4bfe 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ccp.php @@ -24,4 +24,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM, YYYY h:mm a', 'LLLL' => 'dddd, D MMMM, YYYY h:mm a', ], + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php index 371353ef8..3fb022198 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/chr_US.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['ᎤᎾᏙᏓᏆᏍᎬ', 'ᎤᎾᏙᏓᏉᏅᎯ', 'ᏔᎵᏁᎢᎦ', 'ᏦᎢᏁᎢᎦ', 'ᏅᎩᏁᎢᎦ', 'ᏧᎾᎩᎶᏍᏗ', 'ᎤᎾᏙᏓᏈᏕᎾ'], 'weekdays_short' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], 'weekdays_min' => ['ᏆᏍᎬ', 'ᏉᏅᎯ', 'ᏔᎵᏁ', 'ᏦᎢᏁ', 'ᏅᎩᏁ', 'ᏧᎾᎩ', 'ᏈᏕᎾ'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ᏌᎾᎴ', 'ᏒᎯᏱᎢᏗᏢ', 'ꮜꮎꮄ', 'ꮢꭿᏹꭲꮧꮲ'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php index acf4dc280..35ac60a95 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ckb.php @@ -12,20 +12,21 @@ /* * Authors: * - Swara Mohammed + * - Kawan Pshtiwan */ $months = [ - 'ڕێبەندان', - 'ڕەشەمە', - 'نەورۆز', - 'گوڵان', - 'جۆزەردان', - 'پوشپەڕ', - 'گەلاوێژ', - 'خەرمانان', - 'ڕەزبەر', - 'گەڵاڕێزان', - 'سەرماوەرز', - 'بەفرانبار', + 'کانونی دووەم', + 'شوبات', + 'ئازار', + 'نیسان', + 'ئایار', + 'حوزەیران', + 'تەمموز', + 'ئاب', + 'ئەیلوول', + 'تشرینی یەکەم', + 'تشرینی دووەم', + 'کانونی یەکەم', ]; return [ diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php index 7e43f9de3..eee9c806b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cmn_TW.php @@ -14,6 +14,7 @@ * - bug-glibc-locales@gnu.org */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'YYYY年MM月DD號', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php index c01e3ccc8..9530d363d 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cs.php @@ -104,6 +104,7 @@ return [ 'months' => ['ledna', 'února', 'března', 'dubna', 'května', 'června', 'července', 'srpna', 'září', 'října', 'listopadu', 'prosince'], 'months_standalone' => ['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'], 'months_short' => ['led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc', 'srp', 'zář', 'říj', 'lis', 'pro'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', 'weekdays' => ['neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota'], 'weekdays_short' => ['ned', 'pon', 'úte', 'stř', 'čtv', 'pát', 'sob'], 'weekdays_min' => ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php index 8aeb73aa8..fe7696853 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cv.php @@ -31,8 +31,8 @@ return [ 'second' => ':count ҫеккунт', 'a_second' => '{1}пӗр-ик ҫеккунт|:count ҫеккунт', 'ago' => ':time каялла', - 'from_now' => function ($time) { - return $time.(preg_match('/сехет$/u', $time) ? 'рен' : (preg_match('/ҫул/u', $time) ? 'тан' : 'ран')); + 'from_now' => static function ($time) { + return $time.(preg_match('/сехет$/u', $time) ? 'рен' : (preg_match('/ҫул/', $time) ? 'тан' : 'ран')); }, 'diff_yesterday' => 'Ӗнер', 'diff_today' => 'Паян', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php index 119274f01..d769d2f5f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/cy.php @@ -16,19 +16,26 @@ * - Daniel Monaghan */ return [ - 'year' => '{1}blwyddyn|]1,Inf[:count flynedd', + 'year' => '{1}:count flwyddyn|[-Inf,Inf]:count flynedd', + 'a_year' => '{1}blwyddyn|[-Inf,Inf]:count flynedd', 'y' => ':countbl', - 'month' => '{1}mis|]1,Inf[:count mis', + 'month' => ':count mis', + 'a_month' => '{1}mis|[-Inf,Inf]:count mis', 'm' => ':countmi', 'week' => ':count wythnos', + 'a_week' => '{1}wythnos|[-Inf,Inf]:count wythnos', 'w' => ':countw', - 'day' => '{1}diwrnod|]1,Inf[:count diwrnod', + 'day' => ':count diwrnod', + 'a_day' => '{1}diwrnod|[-Inf,Inf]:count diwrnod', 'd' => ':countd', - 'hour' => '{1}awr|]1,Inf[:count awr', + 'hour' => ':count awr', + 'a_hour' => '{1}awr|[-Inf,Inf]:count awr', 'h' => ':counth', - 'minute' => '{1}munud|]1,Inf[:count munud', + 'minute' => ':count munud', + 'a_minute' => '{1}munud|[-Inf,Inf]:count munud', 'min' => ':countm', - 'second' => '{1}ychydig eiliadau|]1,Inf[:count eiliad', + 'second' => ':count eiliad', + 'a_second' => '{0,1}ychydig eiliadau|[-Inf,Inf]:count eiliad', 's' => ':counts', 'ago' => ':time yn ôl', 'from_now' => 'mewn :time', @@ -57,7 +64,7 @@ return [ 'lastWeek' => 'dddd [diwethaf am] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { return $number.( $number > 20 ? (\in_array((int) $number, [40, 50, 60, 80, 100], true) ? 'fed' : 'ain') diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php index 322f91d5b..d0e7168e5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/da.php @@ -71,10 +71,10 @@ return [ ], 'ordinal' => ':number.', 'months' => ['januar', 'februar', 'marts', 'april', 'maj', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'december'], - 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + 'months_short' => ['jan.', 'feb.', 'mar.', 'apr.', 'maj', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], 'weekdays' => ['søndag', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag'], 'weekdays_short' => ['søn.', 'man.', 'tir.', 'ons.', 'tor.', 'fre.', 'lør.'], - 'weekdays_min' => ['sø', 'ma', 'ti', 'on', 'to', 'fr', 'lø'], + 'weekdays_min' => ['sø.', 'ma.', 'ti.', 'on.', 'to.', 'fr.', 'lø.'], 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, 'list' => [', ', ' og '], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php index e95ec4bb2..4f8d1e73c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dav.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['Luma lwa K', 'luma lwa p'], 'weekdays' => ['Ituku ja jumwa', 'Kuramuka jimweri', 'Kuramuka kawi', 'Kuramuka kadadu', 'Kuramuka kana', 'Kuramuka kasanu', 'Kifula nguwo'], 'weekdays_short' => ['Jum', 'Jim', 'Kaw', 'Kad', 'Kan', 'Kas', 'Ngu'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php index 3b70750e4..90b1e350c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/de.php @@ -78,6 +78,24 @@ return [ 'diff_before_yesterday' => 'Vorgestern', 'diff_after_tomorrow' => 'Übermorgen', + 'period_recurrences' => 'einmal|:count mal', + 'period_interval' => static function (string $interval = '') { + /** @var string $output */ + $output = preg_replace('/^(ein|eine|1)\s+/u', '', $interval); + + if (preg_match('/^(ein|1)( Monat| Mon.| Tag| Tg.)/u', $interval)) { + return "jeden $output"; + } + + if (preg_match('/^(ein|1)( Jahr| J.)/u', $interval)) { + return "jedes $output"; + } + + return "jede $output"; + }, + 'period_start_date' => 'von :date', + 'period_end_date' => 'bis :date', + 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php index d35972142..f3d43ce38 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/doi_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['ऐतबार', 'सोमबार', 'मंगलबर', 'बुधबार', 'बीरबार', 'शुक्करबार', 'श्नीचरबार'], 'weekdays_short' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], 'weekdays_min' => ['ऐत', 'सोम', 'मंगल', 'बुध', 'बीर', 'शुक्कर', 'श्नीचर'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['सञं', 'सबेर'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php index 4b8d7e1a4..b2062eb66 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dv.php @@ -10,18 +10,18 @@ */ $months = [ - 'ޖެނުއަރީ', - 'ފެބްރުއަރީ', + 'ޖަނަވަރީ', + 'ފެބުރުވަރީ', 'މާރިޗު', - 'އޭޕްރީލު', + 'އެޕްރީލް', 'މޭ', 'ޖޫން', 'ޖުލައި', - 'އޯގަސްޓު', - 'ސެޕްޓެމްބަރު', - 'އޮކްޓޯބަރު', - 'ނޮވެމްބަރު', - 'ޑިސެމްބަރު', + 'އޮގަސްޓު', + 'ސެޕްޓެންބަރު', + 'އޮކްޓޫބަރު', + 'ނޮވެންބަރު', + 'ޑިސެންބަރު', ]; $weekdays = [ @@ -38,6 +38,7 @@ $weekdays = [ * Authors: * - Josh Soref * - Jawish Hameed + * - Saiph Muhammad */ return [ 'year' => ':count '.'އަހަރު', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php index bfbcaf468..5c40142d2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/dz_BT.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['གཟའ་ཟླ་བ་', 'གཟའ་མིག་དམར་', 'གཟའ་ལྷག་ཕ་', 'གཟའ་པུར་བུ་', 'གཟའ་པ་སངས་', 'གཟའ་སྤེན་ཕ་', 'གཟའ་ཉི་མ་'], 'weekdays_short' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], 'weekdays_min' => ['ཟླ་', 'མིར་', 'ལྷག་', 'པུར་', 'སངས་', 'སྤེན་', 'ཉི་'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ངས་ཆ', 'ཕྱི་ཆ'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php index f60bc6f2f..9e7d95771 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ebu.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['KI', 'UT'], 'weekdays' => ['Kiumia', 'Njumatatu', 'Njumaine', 'Njumatano', 'Aramithi', 'Njumaa', 'NJumamothii'], 'weekdays_short' => ['Kma', 'Tat', 'Ine', 'Tan', 'Arm', 'Maa', 'NMM'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php index 7c40f9c12..d312064ba 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/el.php @@ -44,10 +44,17 @@ return [ 'second' => ':count δευτερόλεπτο|:count δευτερόλεπτα', 'a_second' => 'λίγα δευτερόλεπτα|:count δευτερόλεπτα', 's' => ':count δευ.', + 'ago' => 'πριν :time', 'from_now' => 'σε :time', 'after' => ':time μετά', 'before' => ':time πριν', + + 'year_ago' => ':count χρόνο|:count χρόνια', + 'year_from_now' => ':count χρόνο|:count χρόνια', + 'month_ago' => ':count μήνα|:count μήνες', + 'month_from_now' => ':count μήνα|:count μήνες', + 'diff_now' => 'τώρα', 'diff_today' => 'Σήμερα', 'diff_today_regexp' => 'Σήμερα(?:\\s+{})?', @@ -55,6 +62,7 @@ return [ 'diff_yesterday_regexp' => 'Χθες(?:\\s+{})?', 'diff_tomorrow' => 'αύριο', 'diff_tomorrow_regexp' => 'Αύριο(?:\\s+{})?', + 'formats' => [ 'LT' => 'h:mm A', 'LTS' => 'h:mm:ss A', @@ -68,13 +76,9 @@ return [ 'nextDay' => '[Αύριο {}] LT', 'nextWeek' => 'dddd [{}] LT', 'lastDay' => '[Χθες {}] LT', - 'lastWeek' => function (CarbonInterface $current) { - switch ($current->dayOfWeek) { - case 6: - return '[το προηγούμενο] dddd [{}] LT'; - default: - return '[την προηγούμενη] dddd [{}] LT'; - } + 'lastWeek' => static fn (CarbonInterface $current) => match ($current->dayOfWeek) { + 6 => '[το προηγούμενο] dddd [{}] LT', + default => '[την προηγούμενη] dddd [{}] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php index f81f617e3..374547eb5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en.php @@ -17,37 +17,37 @@ */ return [ /* - * {1}, {0} and ]1,Inf[ are not needed as it's the default for English pluralization. + * {1}, {0} and [-Inf,Inf] are not needed as it's the default for English pluralization. * But as some languages are using en.php as a fallback, it's better to specify it * explicitly so those languages also fallback to English pluralization when a unit * is missing. */ - 'year' => '{1}:count year|{0}:count years|]1,Inf[:count years', - 'a_year' => '{1}a year|{0}:count years|]1,Inf[:count years', - 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', - 'month' => '{1}:count month|{0}:count months|]1,Inf[:count months', - 'a_month' => '{1}a month|{0}:count months|]1,Inf[:count months', - 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', - 'week' => '{1}:count week|{0}:count weeks|]1,Inf[:count weeks', - 'a_week' => '{1}a week|{0}:count weeks|]1,Inf[:count weeks', + 'year' => '{1}:count year|{0}:count years|[-Inf,Inf]:count years', + 'a_year' => '{1}a year|{0}:count years|[-Inf,Inf]:count years', + 'y' => '{1}:countyr|{0}:countyrs|[-Inf,Inf]:countyrs', + 'month' => '{1}:count month|{0}:count months|[-Inf,Inf]:count months', + 'a_month' => '{1}a month|{0}:count months|[-Inf,Inf]:count months', + 'm' => '{1}:countmo|{0}:countmos|[-Inf,Inf]:countmos', + 'week' => '{1}:count week|{0}:count weeks|[-Inf,Inf]:count weeks', + 'a_week' => '{1}a week|{0}:count weeks|[-Inf,Inf]:count weeks', 'w' => ':countw', - 'day' => '{1}:count day|{0}:count days|]1,Inf[:count days', - 'a_day' => '{1}a day|{0}:count days|]1,Inf[:count days', + 'day' => '{1}:count day|{0}:count days|[-Inf,Inf]:count days', + 'a_day' => '{1}a day|{0}:count days|[-Inf,Inf]:count days', 'd' => ':countd', - 'hour' => '{1}:count hour|{0}:count hours|]1,Inf[:count hours', - 'a_hour' => '{1}an hour|{0}:count hours|]1,Inf[:count hours', + 'hour' => '{1}:count hour|{0}:count hours|[-Inf,Inf]:count hours', + 'a_hour' => '{1}an hour|{0}:count hours|[-Inf,Inf]:count hours', 'h' => ':counth', - 'minute' => '{1}:count minute|{0}:count minutes|]1,Inf[:count minutes', - 'a_minute' => '{1}a minute|{0}:count minutes|]1,Inf[:count minutes', + 'minute' => '{1}:count minute|{0}:count minutes|[-Inf,Inf]:count minutes', + 'a_minute' => '{1}a minute|{0}:count minutes|[-Inf,Inf]:count minutes', 'min' => ':countm', - 'second' => '{1}:count second|{0}:count seconds|]1,Inf[:count seconds', - 'a_second' => '{1}a few seconds|{0}:count seconds|]1,Inf[:count seconds', + 'second' => '{1}:count second|{0}:count seconds|[-Inf,Inf]:count seconds', + 'a_second' => '{0,1}a few seconds|[-Inf,Inf]:count seconds', 's' => ':counts', - 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', - 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|]1,Inf[:count milliseconds', + 'millisecond' => '{1}:count millisecond|{0}:count milliseconds|[-Inf,Inf]:count milliseconds', + 'a_millisecond' => '{1}a millisecond|{0}:count milliseconds|[-Inf,Inf]:count milliseconds', 'ms' => ':countms', - 'microsecond' => '{1}:count microsecond|{0}:count microseconds|]1,Inf[:count microseconds', - 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|]1,Inf[:count microseconds', + 'microsecond' => '{1}:count microsecond|{0}:count microseconds|[-Inf,Inf]:count microseconds', + 'a_microsecond' => '{1}a microsecond|{0}:count microseconds|[-Inf,Inf]:count microseconds', 'µs' => ':countµs', 'ago' => ':time ago', 'from_now' => ':time from now', @@ -59,7 +59,7 @@ return [ 'diff_tomorrow' => 'tomorrow', 'diff_before_yesterday' => 'before yesterday', 'diff_after_tomorrow' => 'after tomorrow', - 'period_recurrences' => '{1}once|{0}:count times|]1,Inf[:count times', + 'period_recurrences' => '{1}once|{0}:count times|[-Inf,Inf]:count times', 'period_interval' => 'every :interval', 'period_start_date' => 'from :date', 'period_end_date' => 'to :date', @@ -68,7 +68,7 @@ return [ 'weekdays' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 'weekdays_short' => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 'weekdays_min' => ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { $lastDigit = $number % 10; return $number.( @@ -81,7 +81,16 @@ return [ ) ); }, + 'formats' => [ + 'LT' => 'h:mm A', + 'LTS' => 'h:mm:ss A', + 'L' => 'MM/DD/YYYY', + 'LL' => 'MMMM D, YYYY', + 'LLL' => 'MMMM D, YYYY h:mm A', + 'LLLL' => 'dddd, MMMM D, YYYY h:mm A', + ], 'list' => [', ', ' and '], - 'first_day_of_week' => 0, + 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 1, + 'meridiem' => ['AM', 'PM'], ]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_001.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_150.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php index 2c1c64f06..06c6e1ac1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AG.php @@ -17,5 +17,4 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ 'L' => 'DD/MM/YY', ], - 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AI.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AS.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AT.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php index f16bd4f54..d1df6144c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_AU.php @@ -26,6 +26,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY h:mm A', 'LLLL' => 'dddd, D MMMM YYYY h:mm A', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BB.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BE.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BI.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BM.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BS.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BW.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_BZ.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php index e65608688..824571e08 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CA.php @@ -26,4 +26,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'MMMM D, YYYY h:mm A', 'LLLL' => 'dddd, MMMM D, YYYY h:mm A', ], + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CC.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php index 10d9cd8f1..16668ffdf 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CH.php @@ -10,7 +10,6 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CK.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CM.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CX.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php index a44c3508e..7dfb7210e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_CY.php @@ -23,6 +23,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY HH:mm', 'LLLL' => 'dddd, D MMMM YYYY HH:mm', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DE.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DG.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php index 9e8a8c68b..9615e0438 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DK.php @@ -17,6 +17,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ 'L' => 'YYYY-MM-DD', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_DM.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ER.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FI.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FJ.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FK.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_FM.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php index 67d9fd649..6ba710152 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GB.php @@ -25,6 +25,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY HH:mm', 'LLLL' => 'dddd, D MMMM YYYY HH:mm', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GD.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GG.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GH.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GI.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GM.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GU.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_GY.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php index 34aae989b..d0963b4e1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_HK.php @@ -14,5 +14,5 @@ * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org */ return array_replace_recursive(require __DIR__.'/en.php', [ - 'day_of_first_week_of_year' => 1, + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php index c8d3c2fc9..62e5092f8 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IE.php @@ -26,6 +26,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY HH:mm', 'LLLL' => 'dddd D MMMM YYYY HH:mm', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php index e607924e7..b6107a96c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IL.php @@ -26,4 +26,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY HH:mm', 'LLLL' => 'dddd, D MMMM YYYY HH:mm', ], + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IM.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php index 00414e9ad..4a3f03146 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IN.php @@ -22,5 +22,4 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'DD MMM HH:mm', 'LLLL' => 'MMMM DD, YYYY HH:mm', ], - 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_IO.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php index 11457b0c8..6688b137f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ISO.php @@ -18,4 +18,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'YYYY MMMM D HH:mm', 'LLLL' => 'dddd, YYYY MMMM DD HH:mm', ], + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JE.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_JM.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KE.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KI.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KN.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_KY.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LC.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LR.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_LS.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MG.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MH.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MO.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MP.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MS.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MT.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MU.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MW.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_MY.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NA.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NF.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php index 67bceaad7..c337cfc04 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NG.php @@ -13,6 +13,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ 'L' => 'DD/MM/YY', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NL.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NR.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NU.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php index 6a206a0d0..be65cd3e1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_NZ.php @@ -26,6 +26,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY h:mm A', 'LLLL' => 'dddd, D MMMM YYYY h:mm A', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PG.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php index 34aae989b..d0963b4e1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PH.php @@ -14,5 +14,5 @@ * - IBM Globalization Center of Competency, Yamato Software Laboratory bug-glibc-locales@gnu.org */ return array_replace_recursive(require __DIR__.'/en.php', [ - 'day_of_first_week_of_year' => 1, + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PK.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PN.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PR.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_PW.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_RW.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SB.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SC.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SE.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php index 5ee95241d..e31e8264f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SG.php @@ -19,6 +19,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'D MMMM YYYY HH:mm', 'LLLL' => 'dddd, D MMMM YYYY HH:mm', ], - 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SH.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SI.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SL.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SS.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SX.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_SZ.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TC.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TK.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TO.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TT.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TV.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_TZ.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UG.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_UM.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php index f086dc632..135bc0c36 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_US_Posix.php @@ -9,4 +9,4 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return require __DIR__.'/en_US.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VC.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VG.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VI.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php index e2dd81db5..f086dc632 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_VU.php @@ -9,6 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/en.php', [ - 'first_day_of_week' => 1, -]); +return require __DIR__.'/en.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_WS.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php index 48ea9471c..54d8d8818 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZA.php @@ -22,5 +22,4 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LLL' => 'DD MMM HH:mm', 'LLLL' => 'MMMM DD, YYYY HH:mm', ], - 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php index d8a8cb598..c6bc0b2ff 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZM.php @@ -17,6 +17,4 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ 'L' => 'DD/MM/YY', ], - 'first_day_of_week' => 1, - 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php index f086dc632..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/en_ZW.php @@ -9,4 +9,6 @@ * file that was distributed with this source code. */ -return require __DIR__.'/en.php'; +return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php index 1c4fcfd0b..991a42baf 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es.php @@ -72,6 +72,10 @@ return [ 'diff_tomorrow_regexp' => 'mañana(?:\\s+a)?(?:\\s+las)?', 'diff_before_yesterday' => 'anteayer', 'diff_after_tomorrow' => 'pasado mañana', + 'period_recurrences' => 'una vez|:count veces', + 'period_interval' => 'cada :interval', + 'period_start_date' => 'de :date', + 'period_end_date' => 'a :date', 'formats' => [ 'LT' => 'H:mm', 'LTS' => 'H:mm:ss', @@ -81,26 +85,25 @@ return [ 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', ], 'calendar' => [ - 'sameDay' => function (CarbonInterface $current) { + 'sameDay' => static function (CarbonInterface $current) { return '[hoy a la'.($current->hour !== 1 ? 's' : '').'] LT'; }, - 'nextDay' => function (CarbonInterface $current) { + 'nextDay' => static function (CarbonInterface $current) { return '[mañana a la'.($current->hour !== 1 ? 's' : '').'] LT'; }, - 'nextWeek' => function (CarbonInterface $current) { + 'nextWeek' => static function (CarbonInterface $current) { return 'dddd [a la'.($current->hour !== 1 ? 's' : '').'] LT'; }, - 'lastDay' => function (CarbonInterface $current) { + 'lastDay' => static function (CarbonInterface $current) { return '[ayer a la'.($current->hour !== 1 ? 's' : '').'] LT'; }, - 'lastWeek' => function (CarbonInterface $current) { + 'lastWeek' => static function (CarbonInterface $current) { return '[el] dddd [pasado a la'.($current->hour !== 1 ? 's' : '').'] LT'; }, 'sameElse' => 'L', ], 'months' => ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'], - 'months_short' => ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], - 'mmm_suffix' => '.', + 'months_short' => ['ene.', 'feb.', 'mar.', 'abr.', 'may.', 'jun.', 'jul.', 'ago.', 'sep.', 'oct.', 'nov.', 'dic.'], 'ordinal' => ':numberº', 'weekdays' => ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'], 'weekdays_short' => ['dom.', 'lun.', 'mar.', 'mié.', 'jue.', 'vie.', 'sáb.'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php index deae06a19..ef4b01a86 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/es_PH.php @@ -14,7 +14,7 @@ return array_replace_recursive(require __DIR__.'/es.php', [ 'formats' => [ 'LT' => 'h:mm a', 'LTS' => 'h:mm:ss a', - 'L' => 'D/M/yy', + 'L' => 'D/M/YY', 'LL' => 'D MMM YYYY', 'LLL' => 'D [de] MMMM [de] YYYY h:mm a', 'LLLL' => 'dddd, D [de] MMMM [de] YYYY h:mm a', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php index bcf158078..60751dd53 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fil_PH.php @@ -23,6 +23,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Linggo', 'Lunes', 'Martes', 'Miyerkoles', 'Huwebes', 'Biyernes', 'Sabado'], 'weekdays_short' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], 'weekdays_min' => ['Lin', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['N.U.', 'N.H.'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php index 657f2c5bb..fcbb1595e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fo_DK.php @@ -11,7 +11,7 @@ return array_replace_recursive(require __DIR__.'/fo.php', [ 'formats' => [ - 'L' => 'DD.MM.yy', + 'L' => 'DD.MM.YY', 'LL' => 'DD.MM.YYYY', 'LLL' => 'D. MMMM YYYY, HH:mm', 'LLLL' => 'dddd, D. MMMM YYYY, HH:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php index f4c7247b4..da696e79e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr.php @@ -23,6 +23,12 @@ * - Pete Scopes (pdscopes) */ return [ + 'millennium' => ':count millénaire|:count millénaires', + 'a_millennium' => 'un millénaire|:count millénaires', + 'century' => ':count siècle|:count siècles', + 'a_century' => 'un siècle|:count siècles', + 'decade' => ':count décennie|:count décennies', + 'a_decade' => 'une décennie|:count décennies', 'year' => ':count an|:count ans', 'a_year' => 'un an|:count ans', 'y' => ':count an|:count ans', @@ -88,25 +94,14 @@ return [ 'weekdays' => ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'], 'weekdays_short' => ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], 'weekdays_min' => ['di', 'lu', 'ma', 'me', 'je', 've', 'sa'], - 'ordinal' => function ($number, $period) { - switch ($period) { + 'ordinal' => static function ($number, $period) { + return match ($period) { // In French, only the first has to be ordinal, other number remains cardinal // @link https://fr.wikihow.com/%C3%A9crire-la-date-en-fran%C3%A7ais - case 'D': - return $number.($number === 1 ? 'er' : ''); - - default: - case 'M': - case 'Q': - case 'DDD': - case 'd': - return $number.($number === 1 ? 'er' : 'e'); - - // Words with feminine grammatical gender: semaine - case 'w': - case 'W': - return $number.($number === 1 ? 're' : 'e'); - } + 'D' => $number.($number === 1 ? 'er' : ''), + default => $number.($number === 1 ? 'er' : 'e'), + 'w', 'W' => $number.($number === 1 ? 're' : 'e'), + }; }, 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php index f6cafe87c..8ed032847 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_BE.php @@ -13,6 +13,4 @@ * Authors: * - RAP bug-glibc-locales@gnu.org */ -return array_replace_recursive(require __DIR__.'/fr.php', [ - 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], -]); +return require __DIR__.'/fr.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php index 8e37d852b..6dda77226 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fr_LU.php @@ -17,5 +17,4 @@ return array_replace_recursive(require __DIR__.'/fr.php', [ 'formats' => [ 'L' => 'DD.MM.YYYY', ], - 'months_short' => ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jui', 'aoû', 'sep', 'oct', 'nov', 'déc'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php index 0147a596a..017df98b2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fur_IT.php @@ -12,15 +12,16 @@ /* * Authors: * - Pablo Saratxaga pablo@mandrakesoft.com + * - Kamthorn Krairaksa */ return [ 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', 'L' => 'DD. MM. YY', - 'LL' => 'DD di MMMM dal YYYY', - 'LLL' => 'DD di MMM HH:mm', - 'LLLL' => 'DD di MMMM dal YYYY HH:mm', + 'LL' => 'DD [di] MMMM [dal] YYYY', + 'LLL' => 'DD [di] MMM HH:mm', + 'LLLL' => 'DD [di] MMMM [dal] YYYY HH:mm', ], 'months' => ['zenâr', 'fevrâr', 'març', 'avrîl', 'mai', 'jugn', 'lui', 'avost', 'setembar', 'otubar', 'novembar', 'dicembar'], 'months_short' => ['zen', 'fev', 'mar', 'avr', 'mai', 'jug', 'lui', 'avo', 'set', 'otu', 'nov', 'dic'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php index c1b543970..00009a5ea 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy.php @@ -61,12 +61,11 @@ return [ 'lastWeek' => '[ôfrûne] dddd [om] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); }, 'months' => ['jannewaris', 'febrewaris', 'maart', 'april', 'maaie', 'juny', 'july', 'augustus', 'septimber', 'oktober', 'novimber', 'desimber'], - 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'des'], - 'mmm_suffix' => '.', + 'months_short' => ['jan.', 'feb.', 'mrt.', 'apr.', 'mai.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'des.'], 'weekdays' => ['snein', 'moandei', 'tiisdei', 'woansdei', 'tongersdei', 'freed', 'sneon'], 'weekdays_short' => ['si.', 'mo.', 'ti.', 'wo.', 'to.', 'fr.', 'so.'], 'weekdays_min' => ['Si', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'So'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php index 8559d5c23..5cb2c469f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_DE.php @@ -13,7 +13,7 @@ * Authors: * - information from Kenneth Christiansen Kenneth Christiansen, Pablo Saratxaga kenneth@gnu.org, pablo@mandriva.com */ -return array_replace_recursive(require __DIR__.'/en.php', [ +return array_replace_recursive(require __DIR__.'/fy.php', [ 'formats' => [ 'L' => 'DD.MM.YYYY', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php index 01cc96c34..8ab519ab2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/fy_NL.php @@ -22,6 +22,4 @@ return array_replace_recursive(require __DIR__.'/fy.php', [ 'weekdays' => ['Snein', 'Moandei', 'Tiisdei', 'Woansdei', 'Tongersdei', 'Freed', 'Sneon'], 'weekdays_short' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], 'weekdays_min' => ['Sn', 'Mo', 'Ti', 'Wo', 'To', 'Fr', 'Sn'], - 'first_day_of_week' => 1, - 'day_of_first_week_of_year' => 4, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php index 9f07a26ce..013367bd8 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ga.php @@ -67,9 +67,7 @@ return [ 'weekdays' => ['Dé Domhnaigh', 'Dé Luain', 'Dé Máirt', 'Dé Céadaoin', 'Déardaoin', 'Dé hAoine', 'Dé Satharn'], 'weekdays_short' => ['Dom', 'Lua', 'Mái', 'Céa', 'Déa', 'hAo', 'Sat'], 'weekdays_min' => ['Do', 'Lu', 'Má', 'Ce', 'Dé', 'hA', 'Sa'], - 'ordinal' => function ($number) { - return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); - }, + 'ordinal' => static fn ($number) => $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')), 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 4, 'list' => [', ', ' agus '], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php index 63d064ddd..05437bff5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gd.php @@ -60,7 +60,7 @@ return [ 'lastWeek' => 'dddd [seo chaidh] [aig] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { return $number.($number === 1 ? 'd' : ($number % 10 === 2 ? 'na' : 'mh')); }, 'months' => ['Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php index 393300962..857954981 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gez_ET.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚት'], 'weekdays_short' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], 'weekdays_min' => ['እኁድ', 'ሰኑይ', 'ሠሉስ', 'ራብዕ', 'ሐሙስ', 'ዓርበ', 'ቀዳሚ'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ጽባሕ', 'ምሴት'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php index 088b0f281..96bf14561 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gl.php @@ -43,7 +43,7 @@ return [ 'a_second' => 'uns segundos|:count segundos', 's' => ':count seg.', 'ago' => 'hai :time', - 'from_now' => function ($time) { + 'from_now' => static function ($time) { if (str_starts_with($time, 'un')) { return "n$time"; } @@ -68,19 +68,19 @@ return [ 'LLLL' => 'dddd, D [de] MMMM [de] YYYY H:mm', ], 'calendar' => [ - 'sameDay' => function (CarbonInterface $current) { + 'sameDay' => static function (CarbonInterface $current) { return '[hoxe '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; }, - 'nextDay' => function (CarbonInterface $current) { + 'nextDay' => static function (CarbonInterface $current) { return '[mañá '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; }, - 'nextWeek' => function (CarbonInterface $current) { + 'nextWeek' => static function (CarbonInterface $current) { return 'dddd ['.($current->hour !== 1 ? 'ás' : 'á').'] LT'; }, - 'lastDay' => function (CarbonInterface $current) { + 'lastDay' => static function (CarbonInterface $current) { return '[onte '.($current->hour !== 1 ? 'á' : 'a').'] LT'; }, - 'lastWeek' => function (CarbonInterface $current) { + 'lastWeek' => static function (CarbonInterface $current) { return '[o] dddd [pasado '.($current->hour !== 1 ? 'ás' : 'á').'] LT'; }, 'sameElse' => 'L', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php index 612bb8865..ef50d96e2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gom_Latn.php @@ -53,11 +53,9 @@ return [ 'weekdays_short' => ['Ait.', 'Som.', 'Mon.', 'Bud.', 'Bre.', 'Suk.', 'Son.'], 'weekdays_min' => ['Ai', 'Sm', 'Mo', 'Bu', 'Br', 'Su', 'Sn'], - 'ordinal' => function ($number, $period) { - return $number.($period === 'D' ? 'er' : ''); - }, + 'ordinal' => static fn ($number, $period) => $number.($period === 'D' ? 'er' : ''), - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'rati'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php index 8bc431140..16455eded 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/gu.php @@ -54,7 +54,7 @@ return [ 'lastWeek' => '[પાછલા] dddd, LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'રાત'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php index 6230165c1..d39e9ca3b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/guz.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['Ma', 'Mo'], 'weekdays' => ['Chumapiri', 'Chumatato', 'Chumaine', 'Chumatano', 'Aramisi', 'Ichuma', 'Esabato'], 'weekdays_short' => ['Cpr', 'Ctt', 'Cmn', 'Cmt', 'Ars', 'Icm', 'Est'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php index fe2398650..2a3bc9614 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hak_TW.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['上晝', '下晝'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php index cdd36861e..e46993a3c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/haw.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'months' => ['Ianuali', 'Pepeluali', 'Malaki', 'ʻApelila', 'Mei', 'Iune', 'Iulai', 'ʻAukake', 'Kepakemapa', 'ʻOkakopa', 'Nowemapa', 'Kekemapa'], 'months_short' => ['Ian.', 'Pep.', 'Mal.', 'ʻAp.', 'Mei', 'Iun.', 'Iul.', 'ʻAu.', 'Kep.', 'ʻOk.', 'Now.', 'Kek.'], 'weekdays' => ['Lāpule', 'Poʻakahi', 'Poʻalua', 'Poʻakolu', 'Poʻahā', 'Poʻalima', 'Poʻaono'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php index c3fb3e976..6d8f01ee6 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/he.php @@ -60,7 +60,7 @@ return [ 'lastWeek' => '[ביום] dddd [האחרון בשעה] LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour, $minute, $isLower) { + 'meridiem' => static function ($hour, $minute, $isLower) { if ($hour < 5) { return 'לפנות בוקר'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php index 70c57a297..1fc180102 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hi.php @@ -54,7 +54,7 @@ return [ 'lastWeek' => '[पिछले] dddd, LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'रात'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php index 30ad5e74c..54e880e36 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hif_FJ.php @@ -14,6 +14,7 @@ * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'dddd DD MMM YYYY', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php index a5ca758b6..27b3b396a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hne_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['इतवार', 'सोमवार', 'मंगलवार', 'बुधवार', 'बिरसपत', 'सुकरवार', 'सनिवार'], 'weekdays_short' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], 'weekdays_min' => ['इत', 'सोम', 'मंग', 'बुध', 'बिर', 'सुक', 'सनि'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['बिहिनियाँ', 'मंझनियाँ'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php index cfd85fd40..dcf775659 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr.php @@ -70,29 +70,17 @@ return [ 'calendar' => [ 'sameDay' => '[danas u] LT', 'nextDay' => '[sutra u] LT', - 'nextWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[u] [nedjelju] [u] LT'; - case 3: - return '[u] [srijedu] [u] LT'; - case 6: - return '[u] [subotu] [u] LT'; - default: - return '[u] dddd [u] LT'; - } + 'nextWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[u] [nedjelju] [u] LT', + 3 => '[u] [srijedu] [u] LT', + 6 => '[u] [subotu] [u] LT', + default => '[u] dddd [u] LT', }, 'lastDay' => '[jučer u] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - case 3: - return '[prošlu] dddd [u] LT'; - case 6: - return '[prošle] [subote] [u] LT'; - default: - return '[prošli] dddd [u] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0, 3 => '[prošlu] dddd [u] LT', + 6 => '[prošle] [subote] [u] LT', + default => '[prošli] dddd [u] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php index 7763a4587..9b7fae02e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hr_BA.php @@ -12,6 +12,7 @@ /* * Authors: * - DarkoDevelop + * - Kamthorn Krairaksa */ return array_replace_recursive(require __DIR__.'/hr.php', [ 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], @@ -24,7 +25,7 @@ return array_replace_recursive(require __DIR__.'/hr.php', [ 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', - 'L' => 'D. M. yy.', + 'L' => 'D. M. YY.', 'LL' => 'D. MMM YYYY.', 'LLL' => 'D. MMMM YYYY. HH:mm', 'LLLL' => 'dddd, D. MMMM YYYY. HH:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php index b7583eecd..635a30cf9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hu.php @@ -102,11 +102,11 @@ return [ 'calendar' => [ 'sameDay' => '[ma] LT[-kor]', 'nextDay' => '[holnap] LT[-kor]', - 'nextWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + 'nextWeek' => static function (CarbonInterface $date) use ($huWeekEndings) { return '['.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; }, 'lastDay' => '[tegnap] LT[-kor]', - 'lastWeek' => function (CarbonInterface $date) use ($huWeekEndings) { + 'lastWeek' => static function (CarbonInterface $date) use ($huWeekEndings) { return '[múlt '.$huWeekEndings[$date->dayOfWeek].'] LT[-kor]'; }, 'sameElse' => 'L', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php index 8b129947d..8145ba31e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/hy.php @@ -59,18 +59,13 @@ return [ 'lastWeek' => '[անցած] dddd [օրը ժամը] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'DDD': - case 'w': - case 'W': - case 'DDDo': - return $number.($number === 1 ? '-ին' : '-րդ'); - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'DDD', 'w', 'W', 'DDDo' => $number.($number === 1 ? '-ին' : '-րդ'), + default => $number, + }; }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'գիշերվա'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php index afaf78f29..a49a29327 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/id.php @@ -21,25 +21,25 @@ */ return [ 'year' => ':count tahun', - 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'a_year' => '{1}setahun|[-Inf,Inf]:count tahun', 'y' => ':countthn', 'month' => ':count bulan', - 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'a_month' => '{1}sebulan|[-Inf,Inf]:count bulan', 'm' => ':countbln', 'week' => ':count minggu', - 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'a_week' => '{1}seminggu|[-Inf,Inf]:count minggu', 'w' => ':countmgg', 'day' => ':count hari', - 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'a_day' => '{1}sehari|[-Inf,Inf]:count hari', 'd' => ':counthr', 'hour' => ':count jam', - 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'a_hour' => '{1}sejam|[-Inf,Inf]:count jam', 'h' => ':countj', 'minute' => ':count menit', - 'a_minute' => '{1}semenit|]1,Inf[:count menit', + 'a_minute' => '{1}semenit|[-Inf,Inf]:count menit', 'min' => ':countmnt', 'second' => ':count detik', - 'a_second' => '{1}beberapa detik|]1,Inf[:count detik', + 'a_second' => '{1}beberapa detik|[-Inf,Inf]:count detik', 's' => ':countdt', 'ago' => ':time yang lalu', 'from_now' => ':time dari sekarang', @@ -68,7 +68,7 @@ return [ 'lastWeek' => 'dddd [lalu pukul] LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 11) { return 'pagi'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php index a4246c277..592a53f41 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ii.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['ꎸꄑ', 'ꁯꋒ'], 'weekdays' => ['ꑭꆏꑍ', 'ꆏꊂꋍ', 'ꆏꊂꑍ', 'ꆏꊂꌕ', 'ꆏꊂꇖ', 'ꆏꊂꉬ', 'ꆏꊂꃘ'], 'weekdays_short' => ['ꑭꆏ', 'ꆏꋍ', 'ꆏꑍ', 'ꆏꌕ', 'ꆏꇖ', 'ꆏꉬ', 'ꆏꃘ'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php index bb2a109b9..02dbbef22 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ik_CA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Minġuiqsioiq', 'Savałłiq', 'Ilaqtchiioiq', 'Qitchiioiq', 'Sisamiioiq', 'Tallimmiioiq', 'Maqinġuoiq'], 'weekdays_short' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], 'weekdays_min' => ['Min', 'Sav', 'Ila', 'Qit', 'Sis', 'Tal', 'Maq'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => ':count ukiuq', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php index 49875d7ef..c48369517 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/it.php @@ -54,7 +54,7 @@ return [ 'a_microsecond' => 'un microsecondo|:count microsecondi', 'µs' => ':countµs', 'ago' => ':time fa', - 'from_now' => function ($time) { + 'from_now' => static function ($time) { return (preg_match('/^\d.+$/', $time) ? 'tra' : 'in')." $time"; }, 'after' => ':time dopo', @@ -84,13 +84,9 @@ return [ 'nextDay' => '[Domani alle] LT', 'nextWeek' => 'dddd [alle] LT', 'lastDay' => '[Ieri alle] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[la scorsa] dddd [alle] LT'; - default: - return '[lo scorso] dddd [alle] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[la scorsa] dddd [alle] LT', + default => '[lo scorso] dddd [alle] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php index 6ab7e1497..5acee9390 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iu_CA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['ᓈᑦᑎᖑᔭᕐᕕᒃ', 'ᓇᒡᒐᔾᔭᐅ', 'ᓇᒡᒐᔾᔭᐅᓕᖅᑭᑦ', 'ᐱᖓᓲᓕᖅᓯᐅᑦ', 'ᕿᑎᖅᑰᑦ', 'ᐅᓪᓗᕈᓘᑐᐃᓇᖅ', 'ᓯᕙᑖᕕᒃ'], 'weekdays_short' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], 'weekdays_min' => ['ᓈ', 'ᓇ', 'ᓕ', 'ᐱ', 'ᕿ', 'ᐅ', 'ᓯ'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => ':count ᐅᑭᐅᖅ', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php index a26e35062..5dedd3c02 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/iw.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'months' => ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'], 'months_short' => ['ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳', 'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳'], 'weekdays' => ['יום ראשון', 'יום שני', 'יום שלישי', 'יום רביעי', 'יום חמישי', 'יום שישי', 'יום שבת'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php index 1ca675195..a857db5cc 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ja.php @@ -38,7 +38,7 @@ return [ 'minute' => ':count分', 'min' => ':count分', 'second' => ':count秒', - 'a_second' => '{1}数秒|]1,Inf[:count秒', + 'a_second' => '{1}数秒|[-Inf,Inf]:count秒', 's' => ':count秒', 'ago' => ':time前', 'from_now' => ':time後', @@ -59,7 +59,7 @@ return [ 'calendar' => [ 'sameDay' => '[今日] LT', 'nextDay' => '[明日] LT', - 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + 'nextWeek' => static function (CarbonInterface $current, \Carbon\CarbonInterface $other) { if ($other->week !== $current->week) { return '[来週]dddd LT'; } @@ -67,7 +67,7 @@ return [ return 'dddd LT'; }, 'lastDay' => '[昨日] LT', - 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + 'lastWeek' => static function (CarbonInterface $current, \Carbon\CarbonInterface $other) { if ($other->week !== $current->week) { return '[先週]dddd LT'; } @@ -76,15 +76,11 @@ return [ }, 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'd': - case 'D': - case 'DDD': - return $number.'日'; - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'd', 'D', 'DDD' => $number.'日', + default => $number, + }; }, 'meridiem' => ['午前', '午後'], 'months' => ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php index 6a1e77a85..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jgo.php @@ -10,4 +10,5 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php index bcbe044e3..10fe5383c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/jv.php @@ -16,13 +16,20 @@ * - JD Isaacks */ return [ - 'year' => '{1}setaun|]1,Inf[:count taun', - 'month' => '{1}sewulan|]1,Inf[:count wulan', - 'week' => '{1}sakminggu|]1,Inf[:count minggu', - 'day' => '{1}sedinten|]1,Inf[:count dinten', - 'hour' => '{1}setunggal jam|]1,Inf[:count jam', - 'minute' => '{1}setunggal menit|]1,Inf[:count menit', - 'second' => '{1}sawetawis detik|]1,Inf[:count detik', + 'year' => ':count taun', + 'a_year' => '{1}setaun|[-Inf,Inf]:count taun', + 'month' => ':count wulan', + 'a_month' => '{1}sewulan|[-Inf,Inf]:count wulan', + 'week' => ':count minggu', + 'a_week' => '{1}sakminggu|[-Inf,Inf]:count minggu', + 'day' => ':count dina', + 'a_day' => '{1}sedina|[-Inf,Inf]:count dina', + 'hour' => ':count jam', + 'a_hour' => '{1}setunggal jam|[-Inf,Inf]:count jam', + 'minute' => ':count menit', + 'a_minute' => '{1}setunggal menit|[-Inf,Inf]:count menit', + 'second' => ':count detik', + 'a_second' => '{0,1}sawetawis detik|[-Inf,Inf]:count detik', 'ago' => ':time ingkang kepengker', 'from_now' => 'wonten ing :time', 'diff_today' => 'Dinten', @@ -47,7 +54,7 @@ return [ 'lastWeek' => 'dddd [kepengker pukul] LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 11) { return 'enjing'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php index a5d563d3c..b3051d598 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ka.php @@ -30,26 +30,26 @@ use Carbon\CarbonInterface; return [ 'year' => ':count წელი', 'y' => ':count წელი', - 'a_year' => '{1}წელი|]1,Inf[:count წელი', + 'a_year' => '{1}წელი|[-Inf,Inf]:count წელი', 'month' => ':count თვე', 'm' => ':count თვე', - 'a_month' => '{1}თვე|]1,Inf[:count თვე', + 'a_month' => '{1}თვე|[-Inf,Inf]:count თვე', 'week' => ':count კვირა', 'w' => ':count კვირა', - 'a_week' => '{1}კვირა|]1,Inf[:count კვირა', + 'a_week' => '{1}კვირა|[-Inf,Inf]:count კვირა', 'day' => ':count დღე', 'd' => ':count დღე', - 'a_day' => '{1}დღე|]1,Inf[:count დღე', + 'a_day' => '{1}დღე|[-Inf,Inf]:count დღე', 'hour' => ':count საათი', 'h' => ':count საათი', - 'a_hour' => '{1}საათი|]1,Inf[:count საათი', + 'a_hour' => '{1}საათი|[-Inf,Inf]:count საათი', 'minute' => ':count წუთი', 'min' => ':count წუთი', - 'a_minute' => '{1}წუთი|]1,Inf[:count წუთი', + 'a_minute' => '{1}წუთი|[-Inf,Inf]:count წუთი', 'second' => ':count წამი', 's' => ':count წამი', - 'a_second' => '{1}რამდენიმე წამი|]1,Inf[:count წამი', - 'ago' => function ($time) { + 'a_second' => '{1}რამდენიმე წამი|[-Inf,Inf]:count წამი', + 'ago' => static function ($time) { $replacements = [ // year 'წელი' => 'წლის', @@ -71,7 +71,7 @@ return [ return "$time წინ"; }, - 'from_now' => function ($time) { + 'from_now' => static function ($time) { $replacements = [ // year 'წელი' => 'წელიწადში', @@ -93,7 +93,7 @@ return [ return $time; }, - 'after' => function ($time) { + 'after' => static function ($time) { $replacements = [ // year 'წელი' => 'წლის', @@ -115,7 +115,7 @@ return [ return "$time შემდეგ"; }, - 'before' => function ($time) { + 'before' => static function ($time) { $replacements = [ // year 'წელი' => 'წლით', @@ -152,14 +152,14 @@ return [ 'calendar' => [ 'sameDay' => '[დღეს], LT[-ზე]', 'nextDay' => '[ხვალ], LT[-ზე]', - 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + 'nextWeek' => static function (CarbonInterface $current, \Carbon\CarbonInterface $other) { return ($current->isSameWeek($other) ? '' : '[შემდეგ] ').'dddd, LT[-ზე]'; }, 'lastDay' => '[გუშინ], LT[-ზე]', 'lastWeek' => '[წინა] dddd, LT-ზე', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { if ($number === 0) { return $number; } @@ -184,7 +184,7 @@ return [ 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 1, 'list' => [', ', ' და '], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour >= 4) { if ($hour < 11) { return 'დილის'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php index 0fc70d708..4d3a3b258 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kam.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['Ĩyakwakya', 'Ĩyawĩoo'], 'weekdays' => ['Wa kyumwa', 'Wa kwambĩlĩlya', 'Wa kelĩ', 'Wa katatũ', 'Wa kana', 'Wa katano', 'Wa thanthatũ'], 'weekdays_short' => ['Wky', 'Wkw', 'Wkl', 'Wtũ', 'Wkn', 'Wtn', 'Wth'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php index d86afc500..868fd61ae 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ki.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['Kiroko', 'Hwaĩ-inĩ'], 'weekdays' => ['Kiumia', 'Njumatatũ', 'Njumaine', 'Njumatana', 'Aramithi', 'Njumaa', 'Njumamothi'], 'weekdays_short' => ['KMA', 'NTT', 'NMN', 'NMT', 'ART', 'NMA', 'NMM'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php index 59fa9affb..23452dd2a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kk.php @@ -66,7 +66,7 @@ return [ 'lastWeek' => '[Өткен аптаның] dddd [сағат] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { static $suffixes = [ 0 => '-ші', 1 => '-ші', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php index 6a1e77a85..f4cdb67e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kkj.php @@ -10,4 +10,5 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php index 4fed720a3..64c4ae9c7 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kl_GL.php @@ -31,32 +31,32 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 1, - 'year' => '{1}ukioq :count|{0}:count ukiut|]1,Inf[ukiut :count', - 'a_year' => '{1}ukioq|{0}:count ukiut|]1,Inf[ukiut :count', - 'y' => '{1}:countyr|{0}:countyrs|]1,Inf[:countyrs', + 'year' => '{1}ukioq :count|{0}:count ukiut|[-Inf,Inf]ukiut :count', + 'a_year' => '{1}ukioq|{0}:count ukiut|[-Inf,Inf]ukiut :count', + 'y' => '{1}:countyr|{0}:countyrs|[-Inf,Inf]:countyrs', - 'month' => '{1}qaammat :count|{0}:count qaammatit|]1,Inf[qaammatit :count', - 'a_month' => '{1}qaammat|{0}:count qaammatit|]1,Inf[qaammatit :count', - 'm' => '{1}:countmo|{0}:countmos|]1,Inf[:countmos', + 'month' => '{1}qaammat :count|{0}:count qaammatit|[-Inf,Inf]qaammatit :count', + 'a_month' => '{1}qaammat|{0}:count qaammatit|[-Inf,Inf]qaammatit :count', + 'm' => '{1}:countmo|{0}:countmos|[-Inf,Inf]:countmos', - 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', - 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|]1,Inf[:count sap. ak.', + 'week' => '{1}:count sap. ak.|{0}:count sap. ak.|[-Inf,Inf]:count sap. ak.', + 'a_week' => '{1}a sap. ak.|{0}:count sap. ak.|[-Inf,Inf]:count sap. ak.', 'w' => ':countw', - 'day' => '{1}:count ulloq|{0}:count ullut|]1,Inf[:count ullut', - 'a_day' => '{1}a ulloq|{0}:count ullut|]1,Inf[:count ullut', + 'day' => '{1}:count ulloq|{0}:count ullut|[-Inf,Inf]:count ullut', + 'a_day' => '{1}a ulloq|{0}:count ullut|[-Inf,Inf]:count ullut', 'd' => ':countd', - 'hour' => '{1}:count tiimi|{0}:count tiimit|]1,Inf[:count tiimit', - 'a_hour' => '{1}tiimi|{0}:count tiimit|]1,Inf[:count tiimit', + 'hour' => '{1}:count tiimi|{0}:count tiimit|[-Inf,Inf]:count tiimit', + 'a_hour' => '{1}tiimi|{0}:count tiimit|[-Inf,Inf]:count tiimit', 'h' => ':counth', - 'minute' => '{1}:count minutsi|{0}:count minutsit|]1,Inf[:count minutsit', - 'a_minute' => '{1}a minutsi|{0}:count minutsit|]1,Inf[:count minutsit', + 'minute' => '{1}:count minutsi|{0}:count minutsit|[-Inf,Inf]:count minutsit', + 'a_minute' => '{1}a minutsi|{0}:count minutsit|[-Inf,Inf]:count minutsit', 'min' => ':countm', - 'second' => '{1}:count sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', - 'a_second' => '{1}sikunti|{0}:count sikuntit|]1,Inf[:count sikuntit', + 'second' => '{1}:count sikunti|{0}:count sikuntit|[-Inf,Inf]:count sikuntit', + 'a_second' => '{1}sikunti|{0}:count sikuntit|[-Inf,Inf]:count sikuntit', 's' => ':counts', 'ago' => ':time matuma siorna', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php index b9c39968b..de3f44fc5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kln.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['krn', 'koosk'], 'weekdays' => ['Kotisap', 'Kotaai', 'Koaeng’', 'Kosomok', 'Koang’wan', 'Komuut', 'Kolo'], 'weekdays_short' => ['Kts', 'Kot', 'Koo', 'Kos', 'Koa', 'Kom', 'Kol'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php index da790ac85..570703e20 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/km.php @@ -17,19 +17,25 @@ * - Sovichet Tep */ return [ - 'year' => '{1}មួយឆ្នាំ|]1,Inf[:count ឆ្នាំ', + 'year' => ':count ឆ្នាំ', + 'a_year' => '{1}មួយឆ្នាំ|[-Inf,Inf]:count ឆ្នាំ', 'y' => ':count ឆ្នាំ', - 'month' => '{1}មួយខែ|]1,Inf[:count ខែ', + 'month' => ':count ខែ', + 'a_month' => '{1}មួយខែ|[-Inf,Inf]:count ខែ', 'm' => ':count ខែ', - 'week' => ':count សប្ដាហ៍', - 'w' => ':count សប្ដាហ៍', - 'day' => '{1}មួយថ្ងៃ|]1,Inf[:count ថ្ងៃ', + 'week' => ':count សប្តាហ៍', + 'w' => ':count សប្តាហ៍', + 'day' => ':count ថ្ងៃ', + 'a_day' => '{1}មួយថ្ងៃ|[-Inf,Inf]:count ថ្ងៃ', 'd' => ':count ថ្ងៃ', - 'hour' => '{1}មួយម៉ោង|]1,Inf[:count ម៉ោង', + 'hour' => ':count ម៉ោង', + 'a_hour' => '{1}មួយម៉ោង|[-Inf,Inf]:count ម៉ោង', 'h' => ':count ម៉ោង', - 'minute' => '{1}មួយនាទី|]1,Inf[:count នាទី', + 'minute' => ':count នាទី', + 'a_minute' => '{1}មួយនាទី|[-Inf,Inf]:count នាទី', 'min' => ':count នាទី', - 'second' => '{1}ប៉ុន្មានវិនាទី|]1,Inf[:count វិនាទី', + 'second' => ':count វិនាទី', + 'a_second' => '{0,1}ប៉ុន្មានវិនាទី|[-Inf,Inf]:count វិនាទី', 's' => ':count វិនាទី', 'ago' => ':timeមុន', 'from_now' => ':timeទៀត', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php index 0d2ad08b4..51660e574 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kn.php @@ -17,13 +17,20 @@ * - rajeevnaikte */ return [ - 'year' => '{1}ಒಂದು ವರ್ಷ|]1,Inf[:count ವರ್ಷ', - 'month' => '{1}ಒಂದು ತಿಂಗಳು|]1,Inf[:count ತಿಂಗಳು', - 'week' => '{1}ಒಂದು ವಾರ|]1,Inf[:count ವಾರಗಳು', - 'day' => '{1}ಒಂದು ದಿನ|]1,Inf[:count ದಿನ', - 'hour' => '{1}ಒಂದು ಗಂಟೆ|]1,Inf[:count ಗಂಟೆ', - 'minute' => '{1}ಒಂದು ನಿಮಿಷ|]1,Inf[:count ನಿಮಿಷ', - 'second' => '{1}ಕೆಲವು ಕ್ಷಣಗಳು|]1,Inf[:count ಸೆಕೆಂಡುಗಳು', + 'year' => '{1}:count ವರ್ಷ|[-Inf,Inf]:count ವರ್ಷಗಳು', + 'a_year' => '{1}ಒಂದು ವರ್ಷ|[-Inf,Inf]:count ವರ್ಷಗಳು', + 'month' => ':count ತಿಂಗಳು', + 'a_month' => '{1}ಒಂದು ತಿಂಗಳು|[-Inf,Inf]:count ತಿಂಗಳು', + 'week' => '{1}:count ವಾರ|[-Inf,Inf]:count ವಾರಗಳು', + 'a_week' => '{1}ಒಂದು ವಾರ|[-Inf,Inf]:count ವಾರಗಳು', + 'day' => '{1}:count ದಿನ|[-Inf,Inf]:count ದಿನಗಳು', + 'a_day' => '{1}ಒಂದು ದಿನ|[-Inf,Inf]:count ದಿನಗಳು', + 'hour' => '{1}:count ಗಂಟೆ|[-Inf,Inf]:count ಗಂಟೆಗಳು', + 'a_hour' => '{1}ಒಂದು ಗಂಟೆ|[-Inf,Inf]:count ಗಂಟೆಗಳು', + 'minute' => '{1}:count ನಿಮಿಷ|[-Inf,Inf]:count ನಿಮಿಷಗಳು', + 'a_minute' => '{1}ಒಂದು ನಿಮಿಷ|[-Inf,Inf]:count ನಿಮಿಷಗಳು', + 'second' => '{0,1}:count ಸೆಕೆಂಡ್|[-Inf,Inf]:count ಸೆಕೆಂಡುಗಳು', + 'a_second' => '{0,1}ಕೆಲವು ಕ್ಷಣಗಳು|[-Inf,Inf]:count ಸೆಕೆಂಡುಗಳು', 'ago' => ':time ಹಿಂದೆ', 'from_now' => ':time ನಂತರ', 'diff_now' => 'ಈಗ', @@ -47,7 +54,7 @@ return [ 'sameElse' => 'L', ], 'ordinal' => ':numberನೇ', - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'ರಾತ್ರಿ'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php index 4fa623765..8581b716e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ko.php @@ -22,25 +22,25 @@ */ return [ 'year' => ':count년', - 'a_year' => '{1}일년|]1,Inf[:count년', + 'a_year' => '{1}일년|[-Inf,Inf]:count년', 'y' => ':count년', 'month' => ':count개월', - 'a_month' => '{1}한달|]1,Inf[:count개월', + 'a_month' => '{1}한달|[-Inf,Inf]:count개월', 'm' => ':count개월', 'week' => ':count주', - 'a_week' => '{1}일주일|]1,Inf[:count 주', + 'a_week' => '{1}일주일|[-Inf,Inf]:count 주', 'w' => ':count주일', 'day' => ':count일', - 'a_day' => '{1}하루|]1,Inf[:count일', + 'a_day' => '{1}하루|[-Inf,Inf]:count일', 'd' => ':count일', 'hour' => ':count시간', - 'a_hour' => '{1}한시간|]1,Inf[:count시간', + 'a_hour' => '{1}한시간|[-Inf,Inf]:count시간', 'h' => ':count시간', 'minute' => ':count분', - 'a_minute' => '{1}일분|]1,Inf[:count분', + 'a_minute' => '{1}일분|[-Inf,Inf]:count분', 'min' => ':count분', 'second' => ':count초', - 'a_second' => '{1}몇초|]1,Inf[:count초', + 'a_second' => '{1}몇초|[-Inf,Inf]:count초', 's' => ':count초', 'ago' => ':time 전', 'from_now' => ':time 후', @@ -66,20 +66,13 @@ return [ 'lastWeek' => '지난주 dddd LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'd': - case 'D': - case 'DDD': - return $number.'일'; - case 'M': - return $number.'월'; - case 'w': - case 'W': - return $number.'주'; - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'd', 'D', 'DDD' => $number.'일', + 'M' => $number.'월', + 'w', 'W' => $number.'주', + default => $number, + }; }, 'meridiem' => ['오전', '오후'], 'months' => ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php index 92ba844c5..c6110d595 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/kok_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], 'weekdays_short' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], 'weekdays_min' => ['आयतार', 'सोमार', 'मंगळवार', 'बुधवार', 'बेरेसतार', 'शुकरार', 'शेनवार'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['म.पू.', 'म.नं.'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php index ce9d5d4ae..4ec598fd1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['آتهوار', 'ژءندروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], 'weekdays_short' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], 'weekdays_min' => ['آتهوار', 'ژءنتروار', 'بوءںوار', 'بودهوار', 'برىسوار', 'جمع', 'بٹوار'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['دوپھربرونھ', 'دوپھرپتھ'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php index a2ae8b64f..0708f3ff1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ks_IN@devanagari.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['आथवार', 'चॅ़दुरवार', 'बोमवार', 'ब्वदवार', 'ब्रसवार', 'शोकुरवार', 'बटुवार'], 'weekdays_short' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], 'weekdays_min' => ['आथ ', 'चॅ़दुर', 'बोम', 'ब्वद', 'ब्रस', 'शोकुर', 'बटु'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php index 189960c8a..074b07609 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ku.php @@ -14,20 +14,36 @@ * - Unicode, Inc. */ -return [ +return array_replace_recursive(require __DIR__.'/en.php', [ 'ago' => 'berî :time', 'from_now' => 'di :time de', 'after' => ':time piştî', 'before' => ':time berê', 'year' => ':count sal', + 'a_year' => ':count sal', + 'y' => ':count sal', 'year_ago' => ':count salê|:count salan', + 'y_ago' => ':count salê|:count salan', 'year_from_now' => 'salekê|:count salan', + 'y_from_now' => 'salekê|:count salan', 'month' => ':count meh', + 'a_month' => ':count meh', + 'm' => ':count meh', 'week' => ':count hefte', + 'a_week' => ':count hefte', + 'w' => ':count hefte', 'day' => ':count roj', + 'a_day' => ':count roj', + 'd' => ':count roj', 'hour' => ':count saet', + 'a_hour' => ':count saet', + 'h' => ':count saet', 'minute' => ':count deqîqe', + 'a_minute' => ':count deqîqe', + 'min' => ':count deqîqe', 'second' => ':count saniye', + 'a_second' => ':count saniye', + 's' => ':count saniye', 'months' => ['rêbendanê', 'reşemiyê', 'adarê', 'avrêlê', 'gulanê', 'pûşperê', 'tîrmehê', 'gelawêjê', 'rezberê', 'kewçêrê', 'sermawezê', 'berfanbarê'], 'months_standalone' => ['rêbendan', 'reşemî', 'adar', 'avrêl', 'gulan', 'pûşper', 'tîrmeh', 'gelawêj', 'rezber', 'kewçêr', 'sermawez', 'berfanbar'], 'months_short' => ['rêb', 'reş', 'ada', 'avr', 'gul', 'pûş', 'tîr', 'gel', 'rez', 'kew', 'ser', 'ber'], @@ -35,6 +51,7 @@ return [ 'weekdays_short' => ['yş', 'dş', 'sş', 'çş', 'pş', 'în', 'ş'], 'weekdays_min' => ['Y', 'D', 'S', 'Ç', 'P', 'Î', 'Ş'], 'list' => [', ', ' û '], + 'ordinal' => ':number', 'first_day_of_week' => 6, 'day_of_first_week_of_year' => 1, -]; +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php index e0d1af103..2cb850395 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ky.php @@ -68,7 +68,7 @@ return [ 'lastWeek' => '[Өткен аптанын] dddd [күнү] [саат] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { static $suffixes = [ 0 => '-чү', 1 => '-чи', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php index 7636655e3..72267b734 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lb.php @@ -60,15 +60,12 @@ return [ 'nextDay' => '[Muer um] LT', 'nextWeek' => 'dddd [um] LT', 'lastDay' => '[Gëschter um] LT', - 'lastWeek' => function (CarbonInterface $date) { + 'lastWeek' => static function (CarbonInterface $date) { // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule - switch ($date->dayOfWeek) { - case 2: - case 4: - return '[Leschten] dddd [um] LT'; - default: - return '[Leschte] dddd [um] LT'; - } + return match ($date->dayOfWeek) { + 2, 4 => '[Leschten] dddd [um] LT', + default => '[Leschte] dddd [um] LT', + }; }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php index ae73a97b9..a5485fb38 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lkt.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'month' => ':count haŋwí', // less reliable 'm' => ':count haŋwí', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php index 48715f5c5..a5cc0242c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lo.php @@ -27,7 +27,8 @@ return [ 'h' => ':count ຊມ. ', 'minute' => ':count ນາທີ', 'min' => ':count ນທ. ', - 'second' => '{1}ບໍ່ເທົ່າໃດວິນາທີ|]1,Inf[:count ວິນາທີ', + 'second' => ':count ວິນາທີ', + 'a_second' => '{0,1}ບໍ່ເທົ່າໃດວິນາທີ|[-Inf,Inf]:count ວິນາທີ', 's' => ':count ວິ. ', 'ago' => ':timeຜ່ານມາ', 'from_now' => 'ອີກ :time', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php index 546e67916..31cfc844d 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'minute' => ':count هنر', // less reliable 'min' => ':count هنر', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php index d42f5e971..1ae546b04 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lrc_IQ.php @@ -9,5 +9,4 @@ * file that was distributed with this source code. */ -return array_replace_recursive(require __DIR__.'/lrc.php', [ -]); +return require __DIR__.'/lrc.php'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php index 7d1b6f743..2cfa7d845 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lt.php @@ -33,6 +33,7 @@ * - Kirill Chalkin * - Rolandas * - Justinas (Gamesh) + * - Sam Axe */ return [ 'year' => ':count metai|:count metai|:count metų', @@ -58,21 +59,29 @@ return [ 'minute_ago' => ':count minutę|:count minutes|:count minučių', 'second_ago' => ':count sekundę|:count sekundes|:count sekundžių', - 'year_from_now' => ':count metų', - 'month_from_now' => ':count mėnesio|:count mėnesių|:count mėnesių', - 'week_from_now' => ':count savaitės|:count savaičių|:count savaičių', - 'day_from_now' => ':count dienos|:count dienų|:count dienų', - 'hour_from_now' => ':count valandos|:count valandų|:count valandų', - 'minute_from_now' => ':count minutės|:count minučių|:count minučių', - 'second_from_now' => ':count sekundės|:count sekundžių|:count sekundžių', + 'year_from_now' => ':count metai|:count metai|:count metų', + 'month_from_now' => ':count mėnuo|:count mėnesiai|:count mėnesių', + 'week_from_now' => ':count savaitė|:count savaitės|:count savaičių', + 'day_from_now' => ':count diena|:count dienos|:count dienų', + 'hour_from_now' => ':count valanda|:count valandos|:count valandų', + 'minute_from_now' => ':count minutė|:count minutės|:count minučių', + 'second_from_now' => ':count sekundė|:count sekundės|:count sekundžių', - 'year_after' => ':count metų', - 'month_after' => ':count mėnesio|:count mėnesių|:count mėnesių', - 'week_after' => ':count savaitės|:count savaičių|:count savaičių', - 'day_after' => ':count dienos|:count dienų|:count dienų', - 'hour_after' => ':count valandos|:count valandų|:count valandų', - 'minute_after' => ':count minutės|:count minučių|:count minučių', - 'second_after' => ':count sekundės|:count sekundžių|:count sekundžių', + 'year_after' => ':count metai|:count metai|:count metų', + 'month_after' => ':count mėnuo|:count mėnesiai|:count mėnesių', + 'week_after' => ':count savaitė|:count savaitės|:count savaičių', + 'day_after' => ':count diena|:count dienos|:count dienų', + 'hour_after' => ':count valanda|:count valandos|:count valandų', + 'minute_after' => ':count minutė|:count minutės|:count minučių', + 'second_after' => ':count sekundė|:count sekundės|:count sekundžių', + + 'year_before' => ':count metų', + 'month_before' => ':count mėnesio|:count mėnesių|:count mėnesių', + 'week_before' => ':count savaitės|:count savaičių|:count savaičių', + 'day_before' => ':count dienos|:count dienų|:count dienų', + 'hour_before' => ':count valandos|:count valandų|:count valandų', + 'minute_before' => ':count minutės|:count minučių|:count minučių', + 'second_before' => ':count sekundės|:count sekundžių|:count sekundžių', 'ago' => 'prieš :time', 'from_now' => ':time nuo dabar', @@ -121,15 +130,12 @@ return [ 'lastWeek' => '[Paskutinį] dddd LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { - switch ($number) { - case 0: - return '0-is'; - case 3: - return '3-ias'; - default: - return "$number-as"; - } + 'ordinal' => static function ($number) { + return match ($number) { + 0 => '0-is', + 3 => '3-ias', + default => "$number-as", + }; }, 'meridiem' => ['priešpiet', 'popiet'], ]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php index b55af7318..5d6ec7c9a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luo.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['OD', 'OT'], 'weekdays' => ['Jumapil', 'Wuok Tich', 'Tich Ariyo', 'Tich Adek', 'Tich Ang’wen', 'Tich Abich', 'Ngeso'], 'weekdays_short' => ['JMP', 'WUT', 'TAR', 'TAD', 'TAN', 'TAB', 'NGS'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php index 2b37e3e32..ab92e842a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/luy.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'weekdays' => ['Jumapiri', 'Jumatatu', 'Jumanne', 'Jumatano', 'Murwa wa Kanne', 'Murwa wa Katano', 'Jumamosi'], 'weekdays_short' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], 'weekdays_min' => ['J2', 'J3', 'J4', 'J5', 'Al', 'Ij', 'J1'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php index d5cba7ca0..6ff355125 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lv.php @@ -47,40 +47,34 @@ use Carbon\CarbonInterface; $daysOfWeek = ['svētdiena', 'pirmdiena', 'otrdiena', 'trešdiena', 'ceturtdiena', 'piektdiena', 'sestdiena']; $daysOfWeekLocativum = ['svētdien', 'pirmdien', 'otrdien', 'trešdien', 'ceturtdien', 'piektdien', 'sestdien']; -$transformDiff = function ($input) { - return strtr($input, [ - // Nominative => "pirms/pēc" Dative - 'gads' => 'gada', - 'gadi' => 'gadiem', - 'gadu' => 'gadiem', - 'mēnesis' => 'mēneša', - 'mēneši' => 'mēnešiem', - 'mēnešu' => 'mēnešiem', - 'nedēļa' => 'nedēļas', - 'nedēļas' => 'nedēļām', - 'nedēļu' => 'nedēļām', - 'diena' => 'dienas', - 'dienas' => 'dienām', - 'dienu' => 'dienām', - 'stunda' => 'stundas', - 'stundas' => 'stundām', - 'stundu' => 'stundām', - 'minūte' => 'minūtes', - 'minūtes' => 'minūtēm', - 'minūšu' => 'minūtēm', - 'sekunde' => 'sekundes', - 'sekundes' => 'sekundēm', - 'sekunžu' => 'sekundēm', - ]); -}; +$transformDiff = static fn (string $input) => strtr($input, [ + // Nominative => "pirms/pēc" Dative + 'gads' => 'gada', + 'gadi' => 'gadiem', + 'gadu' => 'gadiem', + 'mēnesis' => 'mēneša', + 'mēneši' => 'mēnešiem', + 'mēnešu' => 'mēnešiem', + 'nedēļa' => 'nedēļas', + 'nedēļas' => 'nedēļām', + 'nedēļu' => 'nedēļām', + 'diena' => 'dienas', + 'dienas' => 'dienām', + 'dienu' => 'dienām', + 'stunda' => 'stundas', + 'stundas' => 'stundām', + 'stundu' => 'stundām', + 'minūte' => 'minūtes', + 'minūtes' => 'minūtēm', + 'minūšu' => 'minūtēm', + 'sekunde' => 'sekundes', + 'sekundes' => 'sekundēm', + 'sekunžu' => 'sekundēm', +]); return [ - 'ago' => function ($time) use ($transformDiff) { - return 'pirms '.$transformDiff($time); - }, - 'from_now' => function ($time) use ($transformDiff) { - return 'pēc '.$transformDiff($time); - }, + 'ago' => static fn (string $time) => 'pirms '.$transformDiff($time), + 'from_now' => static fn (string $time) => 'pēc '.$transformDiff($time), 'year' => '0 gadu|:count gads|:count gadi', 'y' => ':count g.', @@ -159,7 +153,7 @@ return [ 'calendar' => [ 'sameDay' => '[šodien] [plkst.] LT', 'nextDay' => '[rīt] [plkst.] LT', - 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) use ($daysOfWeekLocativum) { + 'nextWeek' => static function (CarbonInterface $current, CarbonInterface $other) use ($daysOfWeekLocativum) { if ($current->week !== $other->week) { return '[nākošo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; } @@ -167,7 +161,7 @@ return [ return '['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; }, 'lastDay' => '[vakar] [plkst.] LT', - 'lastWeek' => function (CarbonInterface $current) use ($daysOfWeekLocativum) { + 'lastWeek' => static function (CarbonInterface $current) use ($daysOfWeekLocativum) { return '[pagājušo] ['.$daysOfWeekLocativum[$current->dayOfWeek].'] [plkst.] LT'; }, 'sameElse' => 'L', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php index 3b1493eef..771394e83 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/lzh_TW.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['週日', '週一', '週二', '週三', '週四', '週五', '週六'], 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'alt_numbers' => ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '廿', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '卅', '卅一'], 'meridiem' => ['朝', '暮'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php index 193f67a7e..879776541 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mag_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], 'weekdays_short' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], 'weekdays_min' => ['एतवार', 'सोमार', 'मंगर', 'बुध', 'बिफे', 'सूक', 'सनिचर'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php index 03049d458..3f9bba779 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mai_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['रविदिन', 'सोमदिन', 'मंगलदिन', 'बुधदिन', 'बृहस्पतीदिन', 'शुक्रदिन', 'शनीदिन'], 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पती', 'शुक्र', 'शनी'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php index cbd610c28..ba99156e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mas.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['Ɛnkakɛnyá', 'Ɛndámâ'], 'weekdays' => ['Jumapílí', 'Jumatátu', 'Jumane', 'Jumatánɔ', 'Alaámisi', 'Jumáa', 'Jumamósi'], 'weekdays_short' => ['Jpi', 'Jtt', 'Jnn', 'Jtn', 'Alh', 'Iju', 'Jmo'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php index 2e14597fa..9b4ad3be9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mer.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['RŨ', 'ŨG'], 'weekdays' => ['Kiumia', 'Muramuko', 'Wairi', 'Wethatu', 'Wena', 'Wetano', 'Jumamosi'], 'weekdays_short' => ['KIU', 'MRA', 'WAI', 'WET', 'WEN', 'WTN', 'JUM'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php index 2d27b4578..ef51ce7dc 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mfe_MU.php @@ -14,6 +14,7 @@ * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'DD/MM/YY', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php index 2a80960d7..a4b624c46 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mgh.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['wichishu', 'mchochil’l'], 'weekdays' => ['Sabato', 'Jumatatu', 'Jumanne', 'Jumatano', 'Arahamisi', 'Ijumaa', 'Jumamosi'], 'weekdays_short' => ['Sab', 'Jtt', 'Jnn', 'Jtn', 'Ara', 'Iju', 'Jmo'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php index d822de098..38fe6d046 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mk.php @@ -68,19 +68,13 @@ return [ 'nextDay' => '[Утре во] LT', 'nextWeek' => '[Во] dddd [во] LT', 'lastDay' => '[Вчера во] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - case 3: - case 6: - return '[Изминатата] dddd [во] LT'; - default: - return '[Изминатиот] dddd [во] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0, 3, 6 => '[Изминатата] dddd [во] LT', + default => '[Изминатиот] dddd [во] LT', }, 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { $lastDigit = $number % 10; $last2Digits = $number % 100; if ($number === 0) { diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php index 1abd6c449..a35f96f50 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ml.php @@ -50,7 +50,7 @@ return [ 'lastWeek' => '[കഴിഞ്ഞ] dddd, LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'രാത്രി'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php index 45d430ef9..4dc577c38 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mni_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['নোংমাইজিং', 'নিংথৌকাবা', 'লৈবাকপোকপা', 'য়ুমশকৈশা', 'শগোলশেন', 'ইরাই', 'থাংজ'], 'weekdays_short' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], 'weekdays_min' => ['নোং', 'নিং', 'লৈবাক', 'য়ুম', 'শগোল', 'ইরা', 'থাং'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['এ.ম.', 'প.ম.'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php index 4aaeafd0c..e57e6f514 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/mr.php @@ -57,7 +57,7 @@ return [ 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'रात्री'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php index c9e80854f..fc53ab982 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms.php @@ -21,29 +21,29 @@ */ return [ 'year' => ':count tahun', - 'a_year' => '{1}setahun|]1,Inf[:count tahun', + 'a_year' => '{1}setahun|[-Inf,Inf]:count tahun', 'y' => ':count tahun', 'month' => ':count bulan', - 'a_month' => '{1}sebulan|]1,Inf[:count bulan', + 'a_month' => '{1}sebulan|[-Inf,Inf]:count bulan', 'm' => ':count bulan', 'week' => ':count minggu', - 'a_week' => '{1}seminggu|]1,Inf[:count minggu', + 'a_week' => '{1}seminggu|[-Inf,Inf]:count minggu', 'w' => ':count minggu', 'day' => ':count hari', - 'a_day' => '{1}sehari|]1,Inf[:count hari', + 'a_day' => '{1}sehari|[-Inf,Inf]:count hari', 'd' => ':count hari', 'hour' => ':count jam', - 'a_hour' => '{1}sejam|]1,Inf[:count jam', + 'a_hour' => '{1}sejam|[-Inf,Inf]:count jam', 'h' => ':count jam', 'minute' => ':count minit', - 'a_minute' => '{1}seminit|]1,Inf[:count minit', + 'a_minute' => '{1}seminit|[-Inf,Inf]:count minit', 'min' => ':count minit', 'second' => ':count saat', - 'a_second' => '{1}beberapa saat|]1,Inf[:count saat', + 'a_second' => '{1}beberapa saat|[-Inf,Inf]:count saat', 'millisecond' => ':count milisaat', - 'a_millisecond' => '{1}semilisaat|]1,Inf[:count milliseconds', + 'a_millisecond' => '{1}semilisaat|[-Inf,Inf]:count milliseconds', 'microsecond' => ':count mikrodetik', - 'a_microsecond' => '{1}semikrodetik|]1,Inf[:count mikrodetik', + 'a_microsecond' => '{1}semikrodetik|[-Inf,Inf]:count mikrodetik', 's' => ':count saat', 'ago' => ':time yang lepas', 'from_now' => ':time dari sekarang', @@ -74,7 +74,7 @@ return [ 'lastWeek' => 'dddd [lepas pukul] LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 1) { return 'tengah malam'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php index ef837a2d7..b8833fc48 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_BN.php @@ -13,7 +13,7 @@ return array_replace_recursive(require __DIR__.'/ms.php', [ 'formats' => [ 'LT' => 'h:mm a', 'LTS' => 'h:mm:ss a', - 'L' => 'D/MM/yy', + 'L' => 'D/MM/YY', 'LL' => 'D MMM YYYY', 'LLL' => 'D MMMM YYYY, h:mm a', 'LLLL' => 'dd MMMM YYYY, h:mm a', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php index 77cb83d28..0fe71b979 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ms_SG.php @@ -13,7 +13,7 @@ return array_replace_recursive(require __DIR__.'/ms.php', [ 'formats' => [ 'LT' => 'h:mm a', 'LTS' => 'h:mm:ss a', - 'L' => 'D/MM/yy', + 'L' => 'D/MM/YY', 'LL' => 'D MMM YYYY', 'LLL' => 'D MMMM YYYY, h:mm a', 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php index bbdfba401..fe0b252bd 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/my.php @@ -16,19 +16,25 @@ * - Nay Lin Aung */ return [ - 'year' => '{1}တစ်နှစ်|]1,Inf[:count နှစ်', + 'year' => ':count နှစ်', + 'a_year' => '{1}တစ်နှစ်|[-Inf,Inf]:count နှစ်', 'y' => ':count နှစ်', - 'month' => '{1}တစ်လ|]1,Inf[:count လ', + 'month' => ':count လ', + 'a_month' => '{1}တစ်လ|[-Inf,Inf]:count လ', 'm' => ':count လ', 'week' => ':count ပတ်', 'w' => ':count ပတ်', - 'day' => '{1}တစ်ရက်|]1,Inf[:count ရက်', + 'day' => ':count ရက်', + 'a_day' => '{1}တစ်ရက်|[-Inf,Inf]:count ရက်', 'd' => ':count ရက်', - 'hour' => '{1}တစ်နာရီ|]1,Inf[:count နာရီ', + 'hour' => ':count နာရီ', + 'a_hour' => '{1}တစ်နာရီ|[-Inf,Inf]:count နာရီ', 'h' => ':count နာရီ', - 'minute' => '{1}တစ်မိနစ်|]1,Inf[:count မိနစ်', + 'minute' => ':count မိနစ်', + 'a_minute' => '{1}တစ်မိနစ်|[-Inf,Inf]:count မိနစ်', 'min' => ':count မိနစ်', - 'second' => '{1}စက္ကန်.အနည်းငယ်|]1,Inf[:count စက္ကန့်', + 'second' => ':count စက္ကန့်', + 'a_second' => '{0,1}စက္ကန်.အနည်းငယ်|[-Inf,Inf]:count စက္ကန့်', 's' => ':count စက္ကန့်', 'ago' => 'လွန်ခဲ့သော :time က', 'from_now' => 'လာမည့် :time မှာ', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php index 5c50aa48d..4fc654816 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['禮拜日', '禮拜一', '禮拜二', '禮拜三', '禮拜四', '禮拜五', '禮拜六'], 'weekdays_short' => ['日', '一', '二', '三', '四', '五', '六'], 'weekdays_min' => ['日', '一', '二', '三', '四', '五', '六'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['頂晡', '下晡'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php index 99ca2a425..5eecc63a8 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nan_TW@latin.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['lé-pài-ji̍t', 'pài-it', 'pài-jī', 'pài-saⁿ', 'pài-sì', 'pài-gō͘', 'pài-la̍k'], 'weekdays_short' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], 'weekdays_min' => ['lp', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['téng-po͘', 'ē-po͘'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php index f75d9a714..d88633c27 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nd.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'weekdays' => ['Sonto', 'Mvulo', 'Sibili', 'Sithathu', 'Sine', 'Sihlanu', 'Mgqibelo'], 'weekdays_short' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], 'weekdays_min' => ['Son', 'Mvu', 'Sib', 'Sit', 'Sin', 'Sih', 'Mgq'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php index d4caf0e25..998d13f66 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne.php @@ -55,7 +55,7 @@ return [ 'lastWeek' => '[गएको] dddd[,] LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 3) { return 'राति'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php index f68d00e3e..9fc21fa79 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ne_IN.php @@ -13,7 +13,7 @@ return array_replace_recursive(require __DIR__.'/ne.php', [ 'formats' => [ 'LT' => 'h:mm a', 'LTS' => 'h:mm:ss a', - 'L' => 'yy/M/d', + 'L' => 'YY/M/D', 'LL' => 'YYYY MMM D', 'LLL' => 'YYYY MMMM D, h:mm a', 'LLLL' => 'YYYY MMMM D, dddd, h:mm a', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php index 9db88a128..8d06d5092 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nhn_MX.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['teoilhuitl', 'ceilhuitl', 'omeilhuitl', 'yeilhuitl', 'nahuilhuitl', 'macuililhuitl', 'chicuaceilhuitl'], 'weekdays_short' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], 'weekdays_min' => ['teo', 'cei', 'ome', 'yei', 'nau', 'mac', 'chi'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'month' => ':count metztli', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php index 2d737703d..8536ed472 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nl.php @@ -69,7 +69,7 @@ return [ 'diff_after_tomorrow' => 'overmorgen', 'diff_before_yesterday' => 'eergisteren', 'period_recurrences' => ':count keer', - 'period_interval' => function (string $interval = '') { + 'period_interval' => static function (string $interval = '') { /** @var string $output */ $output = preg_replace('/^(een|één|1)\s+/u', '', $interval); @@ -97,12 +97,11 @@ return [ 'lastWeek' => '[afgelopen] dddd [om] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { return $number.(($number === 1 || $number === 8 || $number >= 20) ? 'ste' : 'de'); }, 'months' => ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], - 'months_short' => ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], - 'mmm_suffix' => '.', + 'months_short' => ['jan.', 'feb.', 'mrt.', 'apr.', 'mei', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], 'weekdays' => ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], 'weekdays_short' => ['zo.', 'ma.', 'di.', 'wo.', 'do.', 'vr.', 'za.'], 'weekdays_min' => ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php index 007d23993..413e6a45c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nnh.php @@ -20,9 +20,9 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', - 'L' => 'DD/MM/yy', + 'L' => 'DD/MM/YY', 'LL' => 'D MMM, YYYY', - 'LLL' => '[lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', - 'LLLL' => 'dddd , [lyɛ]̌ʼ d [na] MMMM, YYYY HH:mm', + 'LLL' => '[lyɛ]̌ʼ D [na] MMMM, YYYY HH:mm', + 'LLLL' => 'dddd , [lyɛ]̌ʼ D [na] MMMM, YYYY HH:mm', ], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php index f9a7be82e..509375982 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nr_ZA.php @@ -23,4 +23,5 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays_short' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], 'weekdays_min' => ['Son', 'Mvu', 'Bil', 'Tha', 'Ne', 'Hla', 'Gqi'], 'day_of_first_week_of_year' => 1, + 'first_day_of_week' => 0, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php index b08fe6dcd..93da1e78a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/nso_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['LaMorena', 'Mošupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Mokibelo'], 'weekdays_short' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], 'weekdays_min' => ['Son', 'Moš', 'Bed', 'Rar', 'Ne', 'Hla', 'Mok'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => ':count ngwaga', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php index c9411d69d..858cf77be 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/oc.php @@ -12,6 +12,7 @@ /* * Authors: * - Quentí + * - Quentin PAGÈS */ // @codeCoverageIgnoreStart use Symfony\Component\Translation\PluralizationRules; @@ -84,11 +85,11 @@ return [ 'weekdays' => ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], 'weekdays_short' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], 'weekdays_min' => ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], - 'ordinal' => function ($number, string $period = '') { + 'ordinal' => static function ($number, string $period = '') { $ordinal = [1 => 'èr', 2 => 'nd'][(int) $number] ?? 'en'; - // feminine for year, week, hour, minute, second - if (preg_match('/^[yYwWhHgGis]$/', $period)) { + // feminine for week, hour, minute, second + if (preg_match('/^[wWhHgGis]$/', $period)) { $ordinal .= 'a'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php index b8d5a0b01..3c72dc9e5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/om.php @@ -27,6 +27,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Dilbata', 'Wiixata', 'Qibxata', 'Roobii', 'Kamiisa', 'Jimaata', 'Sanbata'], 'weekdays_short' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], 'weekdays_min' => ['Dil', 'Wix', 'Qib', 'Rob', 'Kam', 'Jim', 'San'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['WD', 'WB'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php index 48b203316..23c2f9e4c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa.php @@ -47,7 +47,7 @@ return [ 'lastWeek' => '[ਪਿਛਲੇ] dddd, LT', 'sameElse' => 'L', ], - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'ਰਾਤ'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php index 7adff5c32..02e5a33ac 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_Guru.php @@ -13,7 +13,7 @@ return array_replace_recursive(require __DIR__.'/pa.php', [ 'formats' => [ 'LT' => 'h:mm a', 'LTS' => 'h:mm:ss a', - 'L' => 'D/M/yy', + 'L' => 'D/M/YY', 'LL' => 'D MMM YYYY', 'LLL' => 'D MMMM YYYY, h:mm a', 'LLLL' => 'dddd, D MMMM YYYY, h:mm a', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php index f9af11c60..400ce6ada 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pa_PK.php @@ -13,7 +13,7 @@ * Authors: * - bug-glibc-locales@gnu.org */ -return array_replace_recursive(require __DIR__.'/en.php', [ +return array_replace_recursive(require __DIR__.'/pa_Arab.php', [ 'formats' => [ 'L' => 'DD/MM/YYYY', ], @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], 'weekdays_short' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], 'weekdays_min' => ['اتوار', 'پير', 'منگل', 'بدھ', 'جمعرات', 'جمعه', 'هفته'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ص', 'ش'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php index b72053549..35381f33a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pl.php @@ -82,32 +82,19 @@ return [ 'calendar' => [ 'sameDay' => '[Dziś o] LT', 'nextDay' => '[Jutro o] LT', - 'nextWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[W niedzielę o] LT'; - case 2: - return '[We wtorek o] LT'; - case 3: - return '[W środę o] LT'; - case 6: - return '[W sobotę o] LT'; - default: - return '[W] dddd [o] LT'; - } + 'nextWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[W niedzielę o] LT', + 2 => '[We wtorek o] LT', + 3 => '[W środę o] LT', + 6 => '[W sobotę o] LT', + default => '[W] dddd [o] LT', }, 'lastDay' => '[Wczoraj o] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[W zeszłą niedzielę o] LT'; - case 3: - return '[W zeszłą środę o] LT'; - case 6: - return '[W zeszłą sobotę o] LT'; - default: - return '[W zeszły] dddd [o] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[W zeszłą niedzielę o] LT', + 3 => '[W zeszłą środę o] LT', + 6 => '[W zeszłą sobotę o] LT', + default => '[W zeszły] dddd [o] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php index a928b28e3..c4a438a27 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ps.php @@ -13,6 +13,7 @@ * Authors: * - Muhammad Nasir Rahimi * - Nassim Nasibullah (spinzar) + * - Kamthorn Krairaksa */ return [ 'year' => ':count کال|:count کاله', @@ -47,7 +48,7 @@ return [ 'formats' => [ 'LT' => 'H:mm', 'LTS' => 'H:mm:ss', - 'L' => 'YYYY/M/d', + 'L' => 'YYYY/M/D', 'LL' => 'YYYY MMM D', 'LLL' => 'د YYYY د MMMM D H:mm', 'LLLL' => 'dddd د YYYY د MMMM D H:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php index bb6359b14..85dbddccd 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/pt.php @@ -84,14 +84,9 @@ return [ 'nextDay' => '[Amanhã às] LT', 'nextWeek' => 'dddd [às] LT', 'lastDay' => '[Ontem às] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - case 6: - return '[Último] dddd [às] LT'; - default: - return '[Última] dddd [às] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0, 6 => '[Último] dddd [às] LT', + default => '[Última] dddd [às] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php index d32291893..b047e5970 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/quz_PE.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['tuminku', 'lunis', 'martis', 'miyirkulis', 'juywis', 'wiyirnis', 'sawatu'], 'weekdays_short' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], 'weekdays_min' => ['tum', 'lun', 'mar', 'miy', 'juy', 'wiy', 'saw'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'minute' => ':count uchuy', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php index 7b4589cd3..4a9f0b9d3 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/raj_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['रविवार', 'सोमवार', 'मंगल्लवार', 'बुधवार', 'बृहस्पतिवार', 'शुक्रवार', 'शनिवार'], 'weekdays_short' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], 'weekdays_min' => ['रवि', 'सोम', 'मंगल', 'बुध', 'बृहस्पति', 'शुक्र', 'शनि'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php index 673b043b2..320bdb4e7 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ru.php @@ -34,17 +34,16 @@ * - AlexWalkerson * - Vladislav UnsealedOne * - dima-bzz + * - Sergey Danilchenko */ use Carbon\CarbonInterface; -$transformDiff = function ($input) { - return strtr($input, [ - 'неделя' => 'неделю', - 'секунда' => 'секунду', - 'минута' => 'минуту', - ]); -}; +$transformDiff = static fn (string $input) => strtr($input, [ + 'неделя' => 'неделю', + 'секунда' => 'секунду', + 'минута' => 'минуту', +]); return [ 'year' => ':count год|:count года|:count лет', @@ -68,18 +67,15 @@ return [ 'second' => ':count секунда|:count секунды|:count секунд', 's' => ':count сек.', 'a_second' => '{1}несколько секунд|:count секунду|:count секунды|:count секунд', - 'ago' => function ($time) use ($transformDiff) { - return $transformDiff($time).' назад'; - }, - 'from_now' => function ($time) use ($transformDiff) { - return 'через '.$transformDiff($time); - }, - 'after' => function ($time) use ($transformDiff) { - return $transformDiff($time).' после'; - }, - 'before' => function ($time) use ($transformDiff) { - return $transformDiff($time).' до'; - }, + 'millisecond' => ':count миллисекунда|:count миллисекунды|:count миллисекунд', + 'a_millisecond' => '{1}миллисекунда|:count миллисекунда|:count миллисекунды|:count миллисекунд', + 'ms' => ':count мс', + 'microsecond' => ':count микросекунда|:count микросекунды|:count микросекунд', + 'a_microsecond' => '{1}микросекунда|:count микросекунда|:count микросекунды|:count микросекунд', + 'ago' => static fn (string $time) => $transformDiff($time).' назад', + 'from_now' => static fn (string $time) => 'через '.$transformDiff($time), + 'after' => static fn (string $time) => $transformDiff($time).' после', + 'before' => static fn (string $time) => $transformDiff($time).' до', 'diff_now' => 'только что', 'diff_today' => 'Сегодня,', 'diff_today_regexp' => 'Сегодня,?(?:\\s+в)?', @@ -100,7 +96,7 @@ return [ 'calendar' => [ 'sameDay' => '[Сегодня, в] LT', 'nextDay' => '[Завтра, в] LT', - 'nextWeek' => function (CarbonInterface $current, CarbonInterface $other) { + 'nextWeek' => static function (CarbonInterface $current, \Carbon\CarbonInterface $other) { if ($current->week !== $other->week) { switch ($current->dayOfWeek) { case 0: @@ -123,7 +119,7 @@ return [ return '[В] dddd, [в] LT'; }, 'lastDay' => '[Вчера, в] LT', - 'lastWeek' => function (CarbonInterface $current, CarbonInterface $other) { + 'lastWeek' => static function (CarbonInterface $current, \Carbon\CarbonInterface $other) { if ($current->week !== $other->week) { switch ($current->dayOfWeek) { case 0: @@ -147,22 +143,15 @@ return [ }, 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'M': - case 'd': - case 'DDD': - return $number.'-й'; - case 'D': - return $number.'-го'; - case 'w': - case 'W': - return $number.'-я'; - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'M', 'd', 'DDD' => $number.'-й', + 'D' => $number.'-го', + 'w', 'W' => $number.'-я', + default => $number, + }; }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'ночи'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php index cfda9a636..f2489e8de 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sa_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['रविवासर:', 'सोमवासर:', 'मंगलवासर:', 'बुधवासर:', 'बृहस्पतिवासरः', 'शुक्रवासर', 'शनिवासर:'], 'weekdays_short' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], 'weekdays_min' => ['रविः', 'सोम:', 'मंगल:', 'बुध:', 'बृहस्पतिः', 'शुक्र', 'शनि:'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php index ff8bf6044..ca3f994d4 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/saq.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['Tesiran', 'Teipa'], 'weekdays' => ['Mderot ee are', 'Mderot ee kuni', 'Mderot ee ong’wan', 'Mderot ee inet', 'Mderot ee ile', 'Mderot ee sapa', 'Mderot ee kwe'], 'weekdays_short' => ['Are', 'Kun', 'Ong', 'Ine', 'Ile', 'Sap', 'Kwe'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php index 632b1af6b..6c3608b2d 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sat_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['सिंगेमाँहाँ', 'ओतेमाँहाँ', 'बालेमाँहाँ', 'सागुनमाँहाँ', 'सारदीमाँहाँ', 'जारुममाँहाँ', 'ञुहुममाँहाँ'], 'weekdays_short' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], 'weekdays_min' => ['सिंगे', 'ओते', 'बाले', 'सागुन', 'सारदी', 'जारुम', 'ञुहुम'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'month' => ':count ńindạ cando', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php index 0022c5a9c..d91e4d852 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sd.php @@ -38,16 +38,22 @@ $weekdays = [ * Authors: * - Narain Sagar * - Sawood Alam - * - Narain Sagar */ return [ - 'year' => '{1}'.'هڪ سال'.'|:count '.'سال', - 'month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا', - 'week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا', - 'day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن', - 'hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ', - 'minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ', - 'second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ', + 'year' => ':count '.'سال', + 'a_year' => '{1}'.'هڪ سال'.'|:count '.'سال', + 'month' => ':count '.'مهينا', + 'a_month' => '{1}'.'هڪ مهينو'.'|:count '.'مهينا', + 'week' => ':count '.'هفتا', + 'a_week' => '{1}'.'ھڪ ھفتو'.'|:count '.'هفتا', + 'day' => ':count '.'ڏينهن', + 'a_day' => '{1}'.'هڪ ڏينهن'.'|:count '.'ڏينهن', + 'hour' => ':count '.'ڪلاڪ', + 'a_hour' => '{1}'.'هڪ ڪلاڪ'.'|:count '.'ڪلاڪ', + 'minute' => ':count '.'منٽ', + 'a_minute' => '{1}'.'هڪ منٽ'.'|:count '.'منٽ', + 'second' => ':count '.'سيڪنڊ', + 'a_second' => '{1}'.'چند سيڪنڊ'.'|:count '.'سيڪنڊ', 'ago' => ':time اڳ', 'from_now' => ':time پوء', 'diff_yesterday' => 'ڪالهه', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php index babf9afb0..9d39bcad3 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/seh.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'weekdays' => ['Dimingu', 'Chiposi', 'Chipiri', 'Chitatu', 'Chinai', 'Chishanu', 'Sabudu'], 'weekdays_short' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], 'weekdays_min' => ['Dim', 'Pos', 'Pir', 'Tat', 'Nai', 'Sha', 'Sab'], @@ -19,8 +20,8 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', 'L' => 'D/M/YYYY', - 'LL' => 'd [de] MMM [de] YYYY', - 'LLL' => 'd [de] MMMM [de] YYYY HH:mm', - 'LLLL' => 'dddd, d [de] MMMM [de] YYYY HH:mm', + 'LL' => 'D [de] MMM [de] YYYY', + 'LLL' => 'D [de] MMMM [de] YYYY HH:mm', + 'LLLL' => 'dddd, D [de] MMMM [de] YYYY HH:mm', ], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php index e03b50675..d65f90ec2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sh.php @@ -9,16 +9,6 @@ * file that was distributed with this source code. */ -// @codeCoverageIgnoreStart -use Symfony\Component\Translation\PluralizationRules; - -if (class_exists('Symfony\\Component\\Translation\\PluralizationRules')) { - PluralizationRules::set(static function ($number) { - return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); - }, 'sh'); -} -// @codeCoverageIgnoreEnd - /* * Authors: * - Томица Кораћ diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php index f399acf0d..9eeba47fe 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shn_MM.php @@ -14,6 +14,7 @@ * - ubuntu Myanmar LoCo Team https://ubuntu-mm.net Bone Pyae Sone bone.burma@mail.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'OY MMM OD dddd', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php index 08d385e66..f41c34df6 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/shs_CA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Sxetspesq̓t', 'Spetkesq̓t', 'Selesq̓t', 'Skellesq̓t', 'Smesesq̓t', 'Stselkstesq̓t', 'Stqmekstesq̓t'], 'weekdays_short' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], 'weekdays_min' => ['Sxe', 'Spe', 'Sel', 'Ske', 'Sme', 'Sts', 'Stq'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => ':count sqlélten', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php index 636bf6914..7d14ca6c9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/si.php @@ -32,16 +32,16 @@ return [ 'second' => '{1}තත්පර 1|තත්පර :count', 'a_second' => '{1}තත්පර කිහිපයකට|තත්පර :count', 'ago' => ':time කට පෙර', - 'from_now' => function ($time) { - if (preg_match('/දින \d/u', $time)) { + 'from_now' => static function ($time) { + if (preg_match('/දින \d+/u', $time)) { return $time.' න්'; } return $time.' කින්'; }, 'before' => ':time කට පෙර', - 'after' => function ($time) { - if (preg_match('/දින \d/u', $time)) { + 'after' => static function ($time) { + if (preg_match('/දින \d+/u', $time)) { return $time.' න්'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php index 1296f9be7..5e9632d19 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sid_ET.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Sambata', 'Sanyo', 'Maakisanyo', 'Roowe', 'Hamuse', 'Arbe', 'Qidaame'], 'weekdays_short' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], 'weekdays_min' => ['Sam', 'San', 'Mak', 'Row', 'Ham', 'Arb', 'Qid'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['soodo', 'hawwaro'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php index f9702e960..051e93564 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sk.php @@ -32,6 +32,7 @@ * - Jakub ADAMEC * - Marek Adamický * - AlterwebStudio + * - Peter Kundis */ use Carbon\CarbonInterface; @@ -56,13 +57,15 @@ $ago = function ($time) { ]; $replacementsPlural = [ - '/\bhodiny\b/' => 'hodinami', - '/\bminúty\b/' => 'minútami', - '/\bsekundy\b/' => 'sekundami', + '/\b(?:hodiny|hodín)\b/' => 'hodinami', + '/\b(?:minúty|minút)\b/' => 'minútami', + '/\b(?:sekundy|sekúnd)\b/' => 'sekundami', + '/\bdeň\b/' => 'dňom', '/\bdni\b/' => 'dňami', - '/\btýždne\b/' => 'týždňami', - '/\bmesiace\b/' => 'mesiacmi', - '/\broky\b/' => 'rokmi', + '/\bdní\b/u' => 'dňami', + '/\b(?:týždne|týždňov)\b/' => 'týždňami', + '/\b(?:mesiace|mesiacov)\b/' => 'mesiacmi', + '/\b(?:roky|rokov)\b/' => 'rokmi', ]; foreach ($replacements + $replacementsPlural as $pattern => $replacement) { @@ -149,7 +152,9 @@ return [ 'weekdays' => ['nedeľa', 'pondelok', 'utorok', 'streda', 'štvrtok', 'piatok', 'sobota'], 'weekdays_short' => ['ned', 'pon', 'uto', 'str', 'štv', 'pia', 'sob'], 'weekdays_min' => ['ne', 'po', 'ut', 'st', 'št', 'pi', 'so'], - 'months' => ['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'], + 'months' => ['januára', 'februára', 'marca', 'apríla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'], + 'months_standalone' => ['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'], 'months_short' => ['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', 'meridiem' => ['dopoludnia', 'popoludní'], ]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php index 1f1d1b338..1ce435a72 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sl.php @@ -99,7 +99,7 @@ return [ 'nextDay' => '[jutri ob] LT', 'nextWeek' => 'dddd [ob] LT', 'lastDay' => '[včeraj ob] LT', - 'lastWeek' => function (CarbonInterface $date) { + 'lastWeek' => static function (CarbonInterface $date) { switch ($date->dayOfWeek) { case 0: return '[preteklo] [nedeljo] [ob] LT'; @@ -119,8 +119,10 @@ return [ }, 'sameElse' => 'L', ], - 'months' => ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + 'months' => ['januarja', 'februarja', 'marca', 'aprila', 'maja', 'junija', 'julija', 'avgusta', 'septembra', 'oktobra', 'novembra', 'decembra'], + 'months_standalone' => ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], 'months_short' => ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + 'months_regexp' => '/(DD?o?\.?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', 'weekdays' => ['nedelja', 'ponedeljek', 'torek', 'sreda', 'četrtek', 'petek', 'sobota'], 'weekdays_short' => ['ned', 'pon', 'tor', 'sre', 'čet', 'pet', 'sob'], 'weekdays_min' => ['ne', 'po', 'to', 'sr', 'če', 'pe', 'so'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php index f06606871..1568af6ed 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sm_WS.php @@ -14,6 +14,7 @@ * - Samsung Electronics Co., Ltd. akhilesh.k@samsung.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'DD/MM/YYYY', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php index 4f25028a3..095936fc7 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sn.php @@ -10,6 +10,7 @@ */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'meridiem' => ['a', 'p'], 'weekdays' => ['Svondo', 'Muvhuro', 'Chipiri', 'Chitatu', 'China', 'Chishanu', 'Mugovera'], 'weekdays_short' => ['Svo', 'Muv', 'Chp', 'Cht', 'Chn', 'Chs', 'Mug'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php index 578527197..78e487cc5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/so.php @@ -13,10 +13,10 @@ * Author: * - Abdifatah Abdilahi(@abdifatahz) */ -return [ +return array_replace_recursive(require __DIR__.'/en.php', [ 'year' => ':count sanad|:count sanadood', 'a_year' => 'sanad|:count sanadood', - 'y' => '{1}:countsn|{0}:countsns|]1,Inf[:countsn', + 'y' => '{1}:countsn|{0}:countsns|[-Inf,Inf]:countsn', 'month' => ':count bil|:count bilood', 'a_month' => 'bil|:count bilood', 'm' => ':countbil', @@ -71,4 +71,4 @@ return [ 'lastWeek' => '[Hore] dddd [Markay ahayd] LT', 'sameElse' => 'L', ], -]; +]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php index 68ba663aa..f1908a22f 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr.php @@ -26,7 +26,7 @@ return [ 'year' => ':count godina|:count godine|:count godina', 'y' => ':count g.', 'month' => ':count mesec|:count meseca|:count meseci', - 'm' => ':count mj.', + 'm' => ':count mes.', 'week' => ':count nedelja|:count nedelje|:count nedelja', 'w' => ':count ned.', 'day' => ':count dan|:count dana|:count dana', @@ -37,15 +37,16 @@ return [ 'min' => ':count min.', 'second' => ':count sekundu|:count sekunde|:count sekundi', 's' => ':count sek.', + 'ago' => 'pre :time', 'from_now' => 'za :time', 'after' => 'nakon :time', 'before' => 'pre :time', - 'year_from_now' => ':count godinu|:count godine|:count godina', 'year_ago' => ':count godinu|:count godine|:count godina', - 'week_from_now' => ':count nedelju|:count nedelje|:count nedelja', + 'year_from_now' => ':count godinu|:count godine|:count godina', 'week_ago' => ':count nedelju|:count nedelje|:count nedelja', + 'week_from_now' => ':count nedelju|:count nedelje|:count nedelja', 'diff_now' => 'upravo sada', 'diff_today' => 'danas', @@ -67,36 +68,21 @@ return [ 'calendar' => [ 'sameDay' => '[danas u] LT', 'nextDay' => '[sutra u] LT', - 'nextWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[u nedelju u] LT'; - case 3: - return '[u sredu u] LT'; - case 6: - return '[u subotu u] LT'; - default: - return '[u] dddd [u] LT'; - } + 'nextWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[u nedelju u] LT', + 3 => '[u sredu u] LT', + 6 => '[u subotu u] LT', + default => '[u] dddd [u] LT', }, 'lastDay' => '[juče u] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[prošle nedelje u] LT'; - case 1: - return '[prošlog ponedeljka u] LT'; - case 2: - return '[prošlog utorka u] LT'; - case 3: - return '[prošle srede u] LT'; - case 4: - return '[prošlog četvrtka u] LT'; - case 5: - return '[prošlog petka u] LT'; - default: - return '[prošle subote u] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[prošle nedelje u] LT', + 1 => '[prošlog ponedeljka u] LT', + 2 => '[prošlog utorka u] LT', + 3 => '[prošle srede u] LT', + 4 => '[prošlog četvrtka u] LT', + 5 => '[prošlog petka u] LT', + default => '[prošle subote u] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php index 8becbc576..fe42d5abc 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl.php @@ -66,36 +66,21 @@ return [ 'calendar' => [ 'sameDay' => '[данас у] LT', 'nextDay' => '[сутра у] LT', - 'nextWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[у недељу у] LT'; - case 3: - return '[у среду у] LT'; - case 6: - return '[у суботу у] LT'; - default: - return '[у] dddd [у] LT'; - } + 'nextWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[у недељу у] LT', + 3 => '[у среду у] LT', + 6 => '[у суботу у] LT', + default => '[у] dddd [у] LT', }, 'lastDay' => '[јуче у] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[прошле недеље у] LT'; - case 1: - return '[прошлог понедељка у] LT'; - case 2: - return '[прошлог уторка у] LT'; - case 3: - return '[прошле среде у] LT'; - case 4: - return '[прошлог четвртка у] LT'; - case 5: - return '[прошлог петка у] LT'; - default: - return '[прошле суботе у] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[прошле недеље у] LT', + 1 => '[прошлог понедељка у] LT', + 2 => '[прошлог уторка у] LT', + 3 => '[прошле среде у] LT', + 4 => '[прошлог четвртка у] LT', + 5 => '[прошлог петка у] LT', + default => '[прошле суботе у] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php index 4b29a45c7..5ebab88ea 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_BA.php @@ -23,7 +23,7 @@ return array_replace_recursive(require __DIR__.'/sr_Cyrl.php', [ 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', - 'L' => 'D.M.yy.', + 'L' => 'D.M.YY.', 'LL' => 'DD.MM.YYYY.', 'LLL' => 'DD. MMMM YYYY. HH:mm', 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php index 28d22fd2c..e34f732a1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Cyrl_ME.php @@ -72,36 +72,21 @@ return [ 'calendar' => [ 'sameDay' => '[данас у] LT', 'nextDay' => '[сутра у] LT', - 'nextWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[у недељу у] LT'; - case 3: - return '[у среду у] LT'; - case 6: - return '[у суботу у] LT'; - default: - return '[у] dddd [у] LT'; - } + 'nextWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[у недељу у] LT', + 3 => '[у среду у] LT', + 6 => '[у суботу у] LT', + default => '[у] dddd [у] LT', }, 'lastDay' => '[јуче у] LT', - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[прошле недеље у] LT'; - case 1: - return '[прошлог понедељка у] LT'; - case 2: - return '[прошлог уторка у] LT'; - case 3: - return '[прошле среде у] LT'; - case 4: - return '[прошлог четвртка у] LT'; - case 5: - return '[прошлог петка у] LT'; - default: - return '[прошле суботе у] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[прошле недеље у] LT', + 1 => '[прошлог понедељка у] LT', + 2 => '[прошлог уторка у] LT', + 3 => '[прошле среде у] LT', + 4 => '[прошлог четвртка у] LT', + 5 => '[прошлог петка у] LT', + default => '[прошле суботе у] LT', }, 'sameElse' => 'L', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php index 95b2770db..f33734847 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_BA.php @@ -23,7 +23,7 @@ return array_replace_recursive(require __DIR__.'/sr_Latn.php', [ 'formats' => [ 'LT' => 'HH:mm', 'LTS' => 'HH:mm:ss', - 'L' => 'D.M.yy.', + 'L' => 'D.M.YY.', 'LL' => 'DD.MM.YYYY.', 'LLL' => 'DD. MMMM YYYY. HH:mm', 'LLLL' => 'dddd, DD. MMMM YYYY. HH:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php index 5b8f2d062..811508018 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sr_Latn_ME.php @@ -40,35 +40,20 @@ return array_replace_recursive(require __DIR__.'/sr.php', [ 'diff_tomorrow' => 'sjutra', 'calendar' => [ 'nextDay' => '[sjutra u] LT', - 'nextWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[u nedjelju u] LT'; - case 3: - return '[u srijedu u] LT'; - case 6: - return '[u subotu u] LT'; - default: - return '[u] dddd [u] LT'; - } + 'nextWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[u nedjelju u] LT', + 3 => '[u srijedu u] LT', + 6 => '[u subotu u] LT', + default => '[u] dddd [u] LT', }, - 'lastWeek' => function (CarbonInterface $date) { - switch ($date->dayOfWeek) { - case 0: - return '[prošle nedjelje u] LT'; - case 1: - return '[prošle nedjelje u] LT'; - case 2: - return '[prošlog utorka u] LT'; - case 3: - return '[prošle srijede u] LT'; - case 4: - return '[prošlog četvrtka u] LT'; - case 5: - return '[prošlog petka u] LT'; - default: - return '[prošle subote u] LT'; - } + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0 => '[prošle nedjelje u] LT', + 1 => '[prošle nedjelje u] LT', + 2 => '[prošlog utorka u] LT', + 3 => '[prošle srijede u] LT', + 4 => '[prošlog četvrtka u] LT', + 5 => '[prošlog petka u] LT', + default => '[prošle subote u] LT', }, ], 'weekdays' => ['nedjelja', 'ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php index 1c52c9bf6..0ec3e8f99 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ss.php @@ -46,7 +46,7 @@ return [ 'lastWeek' => 'dddd [leliphelile] [nga] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { $lastDigit = $number % 10; return $number.( @@ -55,7 +55,7 @@ return [ ) ); }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 11) { return 'ekuseni'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php index 5bce7f20f..5eee2224c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/st_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Sontaha', 'Mantaha', 'Labobedi', 'Laboraro', 'Labone', 'Labohlano', 'Moqebelo'], 'weekdays_short' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], 'weekdays_min' => ['Son', 'Mma', 'Bed', 'Rar', 'Ne', 'Hla', 'Moq'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'week' => ':count Sontaha', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php index 1706c7191..d7e0ddf28 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/sv.php @@ -66,7 +66,7 @@ return [ 'lastWeek' => '[I] dddd[s] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { $lastDigit = $number % 10; return $number.( diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php index c1d89cbb3..c5dd68e79 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta.php @@ -63,7 +63,7 @@ return [ 'sameElse' => 'L', ], 'ordinal' => ':numberவது', - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 2) { return ' யாமம்'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php index a6cd8b517..5d05b5ad1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_MY.php @@ -13,7 +13,7 @@ return array_replace_recursive(require __DIR__.'/ta.php', [ 'formats' => [ 'LT' => 'a h:mm', 'LTS' => 'a h:mm:ss', - 'L' => 'D/M/yy', + 'L' => 'D/M/YY', 'LL' => 'D MMM, YYYY', 'LLL' => 'D MMMM, YYYY, a h:mm', 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php index 7dbedeee8..c7ce38f15 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ta_SG.php @@ -13,7 +13,7 @@ return array_replace_recursive(require __DIR__.'/ta.php', [ 'formats' => [ 'LT' => 'a h:mm', 'LTS' => 'a h:mm:ss', - 'L' => 'D/M/yy', + 'L' => 'D/M/YY', 'LL' => 'D MMM, YYYY', 'LLL' => 'D MMMM, YYYY, a h:mm', 'LLLL' => 'dddd, D MMMM, YYYY, a h:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php index 2ff20e0e6..f2bbf1037 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tcy_IN.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['ಐಥಾರ', 'ಸೋಮಾರ', 'ಅಂಗರೆ', 'ಬುಧಾರ', 'ಗುರುವಾರ', 'ಶುಕ್ರರ', 'ಶನಿವಾರ'], 'weekdays_short' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], 'weekdays_min' => ['ಐ', 'ಸೋ', 'ಅಂ', 'ಬು', 'ಗು', 'ಶು', 'ಶ'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ಕಾಂಡೆ', 'ಬಯ್ಯ'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php index ac38218f3..52d4809e9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/te.php @@ -61,7 +61,7 @@ return [ 'sameElse' => 'L', ], 'ordinal' => ':numberవ', - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'రాత్రి'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php index b7df893c1..57b451352 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tg.php @@ -45,7 +45,7 @@ return [ 'lastWeek' => 'dddd[и] [ҳафтаи гузашта соати] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number) { + 'ordinal' => static function ($number) { if ($number === 0) { // special case for zero return "$number-ıncı"; } @@ -77,7 +77,7 @@ return [ return $number.($suffixes[$number] ?? $suffixes[$number % 10] ?? $suffixes[$number >= 100 ? 100 : -1] ?? ''); }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'шаб'; } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php index 6397f6e46..8a22d08e4 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/th.php @@ -19,6 +19,7 @@ * - RO'KAISAKKON * - Andreas Möller * - nithisa + * - Kamthorn Krairaksa */ return [ 'year' => ':count ปี', @@ -34,7 +35,7 @@ return [ 'minute' => ':count นาที', 'min' => ':count นาที', 'second' => ':count วินาที', - 'a_second' => '{1}ไม่กี่วินาที|]1,Inf[:count วินาที', + 'a_second' => '{1}ไม่กี่วินาที|[-Inf,Inf]:count วินาที', 's' => ':count วินาที', 'ago' => ':timeที่แล้ว', 'from_now' => 'อีก :time', @@ -70,4 +71,16 @@ return [ 'weekdays_short' => ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัส', 'ศุกร์', 'เสาร์'], 'weekdays_min' => ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'], 'list' => [', ', ' และ '], + 'alt_numbers' => ['๐๐', '๐๑', '๐๒', '๐๓', '๐๔', '๐๕', '๐๖', '๐๗', '๐๘', '๐๙', '๑๐', '๑๑', '๑๒', '๑๓', '๑๔', '๑๕', '๑๖', '๑๗', '๑๘', '๑๙', '๒๐', '๒๑', '๒๒', '๒๓', '๒๔', '๒๕', '๒๖', '๒๗', '๒๘', '๒๙', '๓๐', '๓๑', '๓๒', '๓๓', '๓๔', '๓๕', '๓๖', '๓๗', '๓๘', '๓๙', '๔๐', '๔๑', '๔๒', '๔๓', '๔๔', '๔๕', '๔๖', '๔๗', '๔๘', '๔๙', '๕๐', '๕๑', '๕๒', '๕๓', '๕๔', '๕๕', '๕๖', '๕๗', '๕๘', '๕๙', '๖๐', '๖๑', '๖๒', '๖๓', '๖๔', '๖๕', '๖๖', '๖๗', '๖๘', '๖๙', '๗๐', '๗๑', '๗๒', '๗๓', '๗๔', '๗๕', '๗๖', '๗๗', '๗๘', '๗๙', '๘๐', '๘๑', '๘๒', '๘๓', '๘๔', '๘๕', '๘๖', '๘๗', '๘๘', '๘๙', '๙๐', '๙๑', '๙๒', '๙๓', '๙๔', '๙๕', '๙๖', '๙๗', '๙๘', '๙๙'], + 'eras' => [ + [ + // Buddhist Era: 543 BCE (astronomical year -542) is BE 1, so 2026 CE is BE 2569 + 'since' => '-0542-01-01', + 'until' => INF, + 'offset' => 1, + 'name' => 'พุทธศักราช', + 'narrow' => 'พ.ศ.', + 'abbr' => 'พ.ศ.', + ], + ], ]; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php index 34da16278..cdb02b2ca 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/the_NP.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['आइतबार', 'सोमबार', 'मंगलबार', 'बुधबार', 'बिहिबार', 'शुक्रबार', 'शनिबार'], 'weekdays_short' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], 'weekdays_min' => ['आइत', 'सोम', 'मंगल', 'बुध', 'बिहि', 'शुक्र', 'शनि'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['पूर्वाह्न', 'अपराह्न'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php index 024217f2f..a8160698a 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ti_ET.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['ሰንበት', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], 'weekdays_short' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], 'weekdays_min' => ['ሰንበ', 'ሰኑይ', 'ሰሉስ', 'ረቡዕ', 'ሓሙስ', 'ዓርቢ', 'ቀዳም'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ንጉሆ ሰዓተ', 'ድሕር ሰዓት'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php index f949a4300..b1c487a07 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tk_TM.php @@ -15,12 +15,10 @@ * - SuperManPHP * - Maksat Meredow (isadma) */ -$transformDiff = function ($input) { - return strtr($input, [ - 'sekunt' => 'sekunt', - 'hepde' => 'hepde', - ]); -}; +$transformDiff = static fn (string $input) => strtr($input, [ + 'sekunt' => 'sekunt', + 'hepde' => 'hepde', +]); return array_replace_recursive(require __DIR__.'/en.php', [ 'formats' => [ @@ -28,9 +26,9 @@ return array_replace_recursive(require __DIR__.'/en.php', [ ], 'months' => ['Ýanwar', 'Fewral', 'Mart', 'Aprel', 'Maý', 'Iýun', 'Iýul', 'Awgust', 'Sentýabr', 'Oktýabr', 'Noýabr', 'Dekabr'], 'months_short' => ['Ýan', 'Few', 'Mar', 'Apr', 'Maý', 'Iýn', 'Iýl', 'Awg', 'Sen', 'Okt', 'Noý', 'Dek'], - 'weekdays' => ['Duşenbe', 'Sişenbe', 'Çarşenbe', 'Penşenbe', 'Anna', 'Şenbe', 'Ýekşenbe'], - 'weekdays_short' => ['Duş', 'Siş', 'Çar', 'Pen', 'Ann', 'Şen', 'Ýek'], - 'weekdays_min' => ['Du', 'Si', 'Ça', 'Pe', 'An', 'Şe', 'Ýe'], + 'weekdays' => ['Ýekşenbe', 'Duşenbe', 'Sişenbe', 'Çarşenbe', 'Penşenbe', 'Anna', 'Şenbe'], + 'weekdays_short' => ['Ýek', 'Duş', 'Siş', 'Çar', 'Pen', 'Ann', 'Şen'], + 'weekdays_min' => ['Ýe', 'Du', 'Si', 'Ça', 'Pe', 'An', 'Şe'], 'first_day_of_week' => 1, 'day_of_first_week_of_year' => 1, @@ -62,16 +60,8 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 's' => ':count sekunt', 'a_second' => ':count sekunt', - 'ago' => function ($time) use ($transformDiff) { - return $transformDiff($time).' ozal'; - }, - 'from_now' => function ($time) use ($transformDiff) { - return $transformDiff($time).' soňra'; - }, - 'after' => function ($time) use ($transformDiff) { - return $transformDiff($time).' soň'; - }, - 'before' => function ($time) use ($transformDiff) { - return $transformDiff($time).' öň'; - }, + 'ago' => static fn (string $time) => $transformDiff($time).' ozal', + 'from_now' => static fn (string $time) => $transformDiff($time).' soňra', + 'after' => static fn (string $time) => $transformDiff($time).' soň', + 'before' => static fn (string $time) => $transformDiff($time).' öň', ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php index fbf9e6f72..4ecf2446c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tlh.php @@ -23,7 +23,7 @@ return [ 'hour' => '{1}wa’ rep|:count rep', 'minute' => '{1}wa’ tup|:count tup', 'second' => '{1}puS lup|:count lup', - 'ago' => function ($time) { + 'ago' => static function ($time) { $output = strtr($time, [ 'jaj' => 'Hu’', 'jar' => 'wen', @@ -32,7 +32,7 @@ return [ return $output === $time ? "$time ret" : $output; }, - 'from_now' => function ($time) { + 'from_now' => static function ($time) { $output = strtr($time, [ 'jaj' => 'leS', 'jar' => 'waQ', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php index aada7db58..e3df8f68e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tn_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['laTshipi', 'Mosupologo', 'Labobedi', 'Laboraro', 'Labone', 'Labotlhano', 'Lamatlhatso'], 'weekdays_short' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], 'weekdays_min' => ['Tsh', 'Mos', 'Bed', 'Rar', 'Ne', 'Tlh', 'Mat'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => 'dingwaga di le :count', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php index 335c69a85..ce713edba 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/to_TO.php @@ -14,6 +14,7 @@ * - International Components for Unicode akhilesh.k@samsung.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'first_day_of_week' => 0, 'formats' => [ 'L' => 'dddd DD MMM YYYY', ], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php index 5f58c44c0..721b625b5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tpi_PG.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Sande', 'Mande', 'Tunde', 'Trinde', 'Fonde', 'Fraide', 'Sarere'], 'weekdays_short' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], 'weekdays_min' => ['San', 'Man', 'Tun', 'Tri', 'Fon', 'Fra', 'Sar'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['biknait', 'apinun'], diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php index f5d9f4cc5..0cb3aade4 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/tr.php @@ -23,25 +23,25 @@ */ return [ 'year' => ':count yıl', - 'a_year' => '{1}bir yıl|]1,Inf[:count yıl', + 'a_year' => '{1}bir yıl|[-Inf,Inf]:count yıl', 'y' => ':county', 'month' => ':count ay', - 'a_month' => '{1}bir ay|]1,Inf[:count ay', + 'a_month' => '{1}bir ay|[-Inf,Inf]:count ay', 'm' => ':countay', 'week' => ':count hafta', - 'a_week' => '{1}bir hafta|]1,Inf[:count hafta', + 'a_week' => '{1}bir hafta|[-Inf,Inf]:count hafta', 'w' => ':counth', 'day' => ':count gün', - 'a_day' => '{1}bir gün|]1,Inf[:count gün', + 'a_day' => '{1}bir gün|[-Inf,Inf]:count gün', 'd' => ':countg', 'hour' => ':count saat', - 'a_hour' => '{1}bir saat|]1,Inf[:count saat', + 'a_hour' => '{1}bir saat|[-Inf,Inf]:count saat', 'h' => ':countsa', 'minute' => ':count dakika', - 'a_minute' => '{1}bir dakika|]1,Inf[:count dakika', + 'a_minute' => '{1}bir dakika|[-Inf,Inf]:count dakika', 'min' => ':countdk', 'second' => ':count saniye', - 'a_second' => '{1}birkaç saniye|]1,Inf[:count saniye', + 'a_second' => '{1}birkaç saniye|[-Inf,Inf]:count saniye', 's' => ':countsn', 'ago' => ':time önce', 'from_now' => ':time sonra', @@ -71,7 +71,7 @@ return [ 'lastWeek' => '[geçen] dddd [saat] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { + 'ordinal' => static function ($number, $period) { switch ($period) { case 'd': case 'D': diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php index 37a24ec49..327134551 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ts_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Sonto', 'Musumbhunuku', 'Ravumbirhi', 'Ravunharhu', 'Ravumune', 'Ravuntlhanu', 'Mugqivela'], 'weekdays_short' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], 'weekdays_min' => ['Son', 'Mus', 'Bir', 'Har', 'Ne', 'Tlh', 'Mug'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => 'malembe ya :count', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php index 259b99a48..12c9f8a67 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ug.php @@ -46,20 +46,14 @@ return [ 'lastWeek' => '[ئالدىنقى] dddd [سائەت] LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'd': - case 'D': - case 'DDD': - return $number.'-كۈنى'; - case 'w': - case 'W': - return $number.'-ھەپتە'; - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'd', 'D', 'DDD' => $number.'-كۈنى', + 'w', 'W' => $number.'-ھەپتە', + default => $number, + }; }, - 'meridiem' => function ($hour, $minute) { + 'meridiem' => static function ($hour, $minute) { $time = $hour * 100 + $minute; if ($time < 600) { return 'يېرىم كېچە'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php index 4217d16ed..3bedd2ea2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uk.php @@ -11,7 +11,7 @@ use Carbon\CarbonInterface; -$processHoursFunction = function (CarbonInterface $date, string $format) { +$processHoursFunction = static function (CarbonInterface $date, string $format) { return $format.'о'.($date->hour === 11 ? 'б' : '').'] LT'; }; @@ -130,52 +130,30 @@ return [ 'LLLL' => 'dddd, D MMMM YYYY, HH:mm', ], 'calendar' => [ - 'sameDay' => function (CarbonInterface $date) use ($processHoursFunction) { - return $processHoursFunction($date, '[Сьогодні '); - }, - 'nextDay' => function (CarbonInterface $date) use ($processHoursFunction) { - return $processHoursFunction($date, '[Завтра '); - }, - 'nextWeek' => function (CarbonInterface $date) use ($processHoursFunction) { - return $processHoursFunction($date, '[У] dddd ['); - }, - 'lastDay' => function (CarbonInterface $date) use ($processHoursFunction) { - return $processHoursFunction($date, '[Вчора '); - }, - 'lastWeek' => function (CarbonInterface $date) use ($processHoursFunction) { - switch ($date->dayOfWeek) { - case 0: - case 3: - case 5: - case 6: - return $processHoursFunction($date, '[Минулої] dddd ['); - default: - return $processHoursFunction($date, '[Минулого] dddd ['); - } + 'sameDay' => static fn (CarbonInterface $date) => $processHoursFunction($date, '[Сьогодні '), + 'nextDay' => static fn (CarbonInterface $date) => $processHoursFunction($date, '[Завтра '), + 'nextWeek' => static fn (CarbonInterface $date) => $processHoursFunction($date, '[У] dddd ['), + 'lastDay' => static fn (CarbonInterface $date) => $processHoursFunction($date, '[Вчора '), + 'lastWeek' => static fn (CarbonInterface $date) => match ($date->dayOfWeek) { + 0, 3, 5, 6 => $processHoursFunction($date, '[Минулої] dddd ['), + default => $processHoursFunction($date, '[Минулого] dddd ['), }, 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'M': - case 'd': - case 'DDD': - case 'w': - case 'W': - return $number.'-й'; - case 'D': - return $number.'-го'; - default: - return $number; - } + 'ordinal' => static fn ($number, $period) => match ($period) { + 'M', 'd', 'DDD', 'w', 'W' => $number.'-й', + 'D' => $number.'-го', + default => $number, }, - 'meridiem' => function ($hour) { + 'meridiem' => static function ($hour) { if ($hour < 4) { return 'ночі'; } + if ($hour < 12) { return 'ранку'; } + if ($hour < 17) { return 'дня'; } @@ -186,14 +164,14 @@ return [ 'months_standalone' => ['січень', 'лютий', 'березень', 'квітень', 'травень', 'червень', 'липень', 'серпень', 'вересень', 'жовтень', 'листопад', 'грудень'], 'months_short' => ['січ', 'лют', 'бер', 'кві', 'тра', 'чер', 'лип', 'сер', 'вер', 'жов', 'лис', 'гру'], 'months_regexp' => '/(D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|L{2,4}|l{2,4})/', - 'weekdays' => function (CarbonInterface $date, $format, $index) { + 'weekdays' => static function (CarbonInterface $date, $format, $index) { static $words = [ 'nominative' => ['неділя', 'понеділок', 'вівторок', 'середа', 'четвер', 'п’ятниця', 'субота'], 'accusative' => ['неділю', 'понеділок', 'вівторок', 'середу', 'четвер', 'п’ятницю', 'суботу'], 'genitive' => ['неділі', 'понеділка', 'вівторка', 'середи', 'четверга', 'п’ятниці', 'суботи'], ]; - $format = $format ?? ''; + $format ??= ''; $nounCase = preg_match('/(\[(В|в|У|у)\])\s+dddd/u', $format) ? 'accusative' : ( diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php index fa5c374e3..161a1ec88 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/unm_US.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['kentuwei', 'manteke', 'tusteke', 'lelai', 'tasteke', 'pelaiteke', 'sateteke'], 'weekdays_short' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], 'weekdays_min' => ['ken', 'man', 'tus', 'lel', 'tas', 'pel', 'sat'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, // Too unreliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php index dc16c2c35..ac960f3f4 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ur.php @@ -46,15 +46,22 @@ $weekdays = [ * - hafezdivandari * - Hossein Jabbari * - nimamo + * - Usman Zahid */ return [ - 'year' => 'ایک سال|:count سال', - 'month' => 'ایک ماہ|:count ماہ', - 'week' => ':count ہفتے', - 'day' => 'ایک دن|:count دن', - 'hour' => 'ایک گھنٹہ|:count گھنٹے', - 'minute' => 'ایک منٹ|:count منٹ', - 'second' => 'چند سیکنڈ|:count سیکنڈ', + 'year' => ':count '.'سال', + 'a_year' => 'ایک سال|:count سال', + 'month' => ':count '.'ماہ', + 'a_month' => 'ایک ماہ|:count ماہ', + 'week' => ':count '.'ہفتے', + 'day' => ':count '.'دن', + 'a_day' => 'ایک دن|:count دن', + 'hour' => ':count '.'گھنٹے', + 'a_hour' => 'ایک گھنٹہ|:count گھنٹے', + 'minute' => ':count '.'منٹ', + 'a_minute' => 'ایک منٹ|:count منٹ', + 'second' => ':count '.'سیکنڈ', + 'a_second' => 'چند سیکنڈ|:count سیکنڈ', 'ago' => ':time قبل', 'from_now' => ':time بعد', 'after' => ':time بعد', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php index 89e997181..8c6999e2c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/uz_Cyrl.php @@ -11,7 +11,7 @@ return array_replace_recursive(require __DIR__.'/uz.php', [ 'formats' => [ - 'L' => 'DD/MM/yy', + 'L' => 'DD/MM/YY', 'LL' => 'D MMM, YYYY', 'LLL' => 'D MMMM, YYYY HH:mm', 'LLLL' => 'dddd, DD MMMM, YYYY HH:mm', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php index 5eb2b9128..d401d9f24 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/ve_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['Swondaha', 'Musumbuluwo', 'Ḽavhuvhili', 'Ḽavhuraru', 'Ḽavhuṋa', 'Ḽavhuṱanu', 'Mugivhela'], 'weekdays_short' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], 'weekdays_min' => ['Swo', 'Mus', 'Vhi', 'Rar', 'ṋa', 'Ṱan', 'Mug'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, // Too unreliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php index a4e619a87..e862f2c59 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/wal_ET.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['ወጋ', 'ሳይኖ', 'ማቆሳኛ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ'], 'weekdays_short' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], 'weekdays_min' => ['ወጋ ', 'ሳይኖ', 'ማቆሳ', 'አሩዋ', 'ሃሙሳ', 'አርባ', 'ቄራ '], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'meridiem' => ['ማለዶ', 'ቃማ'], ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php index 910f8311e..009153c7b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/xh_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['iCawa', 'uMvulo', 'lwesiBini', 'lwesiThathu', 'ulweSine', 'lwesiHlanu', 'uMgqibelo'], 'weekdays_short' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], 'weekdays_min' => ['Caw', 'Mvu', 'Bin', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => ':count ihlobo', // less reliable diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php index f764d36f6..091a0e700 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yi_US.php @@ -14,41 +14,59 @@ * - http://www.uyip.org/ Pablo Saratxaga pablo@mandrakesoft.com */ return array_replace_recursive(require __DIR__.'/en.php', [ + 'year' => '{1}:count יאר|{0}:count יאר|[-Inf,Inf]:count יאר', + 'a_year' => '{1}א יאר|{0}:count יאר|[-Inf,Inf]:count יאר', + 'y' => ':count יאר', + 'month' => '{1}:count חודש|{0}:count חדשים|[-Inf,Inf]:count חדשים', + 'a_month' => '{1}א חודש|{0}:count חדשים|[-Inf,Inf]:count חדשים', + 'm' => '{1}:count חודש|{0}:count חדשים|[-Inf,Inf]:count חדשים', + 'week' => '{1}:count וואך|{0}:count וואכן|[-Inf,Inf]:count וואכן', + 'a_week' => '{1}א וואך|{0}:count וואכן|[-Inf,Inf]:count וואכן', + 'w' => ':count וואך', + 'day' => '{1}:count טאג|{0}:count טעג|[-Inf,Inf]:count טעג', + 'a_day' => '{1}א טאג|{0}:count טעג|[-Inf,Inf]:count טעג', + 'd' => ':count טאג', + 'hour' => ':count שעה', + 'a_hour' => 'א שעה', + 'h' => ':count שעה', + 'minute' => '{1}:count מינוט|{0}:count מינוט|[-Inf,Inf]:count מינוט', + 'a_minute' => '{1}א מינוט|{0}:count מינוט|[-Inf,Inf]:count מינוט', + 'min' => ':count מינוט', + 'second' => '{1}:count סעקונדע|{0}:count סעקונדעס|[-Inf,Inf]:count סעקונדעס', + 'a_second' => '{0,1}א סעקונדע|[-Inf,Inf]:count סעקונדעס', + 's' => ':count סעק', + 'millisecond' => '{1}:count מילי-סעקונדע|{0}:count מילי-סעקונדעס|[-Inf,Inf]:count מילי-סעקונדעס', + 'a_millisecond' => '{1}א מילי-סעקונדע|{0}:count מילי-סעקונדעס|[-Inf,Inf]:count מילי-סעקונדעס', + 'ms' => ':count מס', + 'microsecond' => '{1}:count מיקרא-סעקונדע|{0}:count מיקרא-סעקונדעס|[-Inf,Inf]:count מיקרא-סעקונדעס', + 'a_microsecond' => '{1}א מיקרא-סעקונדע|{0}:count מיקרא-סעקונדעס|[-Inf,Inf]:count מיקרא-סעקונדעס', + 'µs' => ':count מיקרא', + 'ago' => ':time פון יעצט', + 'from_now' => ':time ארום', + 'after' => ':time נאך', + 'before' => ':time פאר', + 'diff_now' => 'ממש יעצט', + 'diff_today' => 'היינט', + 'diff_yesterday' => 'נעכטן', + 'diff_tomorrow' => 'מארגן', + 'diff_before_yesterday' => 'אייער-נעכטן', + 'diff_after_tomorrow' => 'איבער-מארגן', + 'period_recurrences' => '{1}איין מאל|{0}:count מאל|[-Inf,Inf]:count מאל', + 'period_interval' => 'יעדע :interval', + 'period_start_date' => 'פון :date', + 'period_end_date' => 'ביז :date', + 'months' => ['יאנואר', 'פעברואר', 'מארטש', 'אפריל', 'מאי', 'יוני', 'יולי', 'אויגוסט', 'סעפטעמבער', 'אקטאבער', 'נאוועמבער', 'דעצעמבער'], + 'months_short' => ['יאנ\'', 'פעב\'', 'מאר\'', 'אפר\'', 'מאי', 'יוני', 'יולי', 'אויג\'', 'סעפ\'', 'אקט\'', 'נאו\'', 'דעצ\''], + 'weekdays' => ['זונטאג', 'מאנטאג', 'דינסטאג', 'מיטוואך', 'דאנערשטאג', 'פרייטאג', 'שבת'], + 'weekdays_short' => ['זונ\'', 'מאנ\'', 'דינ\'', 'מיט\'', 'דאנ\'', 'פריי\'', 'שבת'], + 'weekdays_min' => ['ז\'', 'מ\'', 'ד\'', 'מ\'', 'ד\'', 'ו\'', 'ש\''], + 'ordinal' => static function ($number) { + return $number.'טע'; + }, + 'list' => [', ', ' און '], 'formats' => [ 'L' => 'DD/MM/YY', ], - 'months' => ['יאַנואַר', 'פֿעברואַר', 'מערץ', 'אַפּריל', 'מיי', 'יוני', 'יולי', 'אויגוסט', 'סעפּטעמבער', 'אקטאבער', 'נאוועמבער', 'דעצעמבער'], - 'months_short' => ['יאַנ', 'פֿעב', 'מאַר', 'אַפּר', 'מײַ ', 'יונ', 'יול', 'אױג', 'סעפּ', 'אָקט', 'נאָװ', 'דעצ'], - 'weekdays' => ['זונטיק', 'מאָנטיק', 'דינסטיק', 'מיטװאָך', 'דאָנערשטיק', 'פֿרײַטיק', 'שבת'], - 'weekdays_short' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], - 'weekdays_min' => ['זונ\'', 'מאָנ\'', 'דינ\'', 'מיט\'', 'דאָנ\'', 'פֿרײַ\'', 'שבת'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, - - 'year' => ':count יאר', - 'y' => ':count יאר', - 'a_year' => ':count יאר', - - 'month' => ':count חודש', - 'm' => ':count חודש', - 'a_month' => ':count חודש', - - 'week' => ':count וואָך', - 'w' => ':count וואָך', - 'a_week' => ':count וואָך', - - 'day' => ':count טאָג', - 'd' => ':count טאָג', - 'a_day' => ':count טאָג', - - 'hour' => ':count שעה', - 'h' => ':count שעה', - 'a_hour' => ':count שעה', - - 'minute' => ':count מינוט', - 'min' => ':count מינוט', - 'a_minute' => ':count מינוט', - - 'second' => ':count סעקונדע', - 's' => ':count סעקונדע', - 'a_second' => ':count סעקונדע', ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php index 0a829810a..7c688a07c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yo.php @@ -27,7 +27,7 @@ return [ 'a_hour' => '{1}wákati kan|wákati :count', 'minute' => 'ìsẹjú :count', 'a_minute' => '{1}ìsẹjú kan|ìsẹjú :count', - 'second' => 'iaayá :count', + 'second' => 'aayá :count', 'a_second' => '{1}ìsẹjú aayá die|aayá :count', 'ago' => ':time kọjá', 'from_now' => 'ní :time', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php index b99ad2e8e..8a1ccf981 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/yuw_PG.php @@ -22,5 +22,6 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['sönda', 'mönda', 'sinda', 'mitiwö', 'sogipbono', 'nenggo', 'söndanggie'], 'weekdays_short' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], 'weekdays_min' => ['sön', 'mön', 'sin', 'mit', 'soi', 'nen', 'sab'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, ]); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php index 9b91785e1..8d8d9d771 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hans.php @@ -37,7 +37,7 @@ return [ 'minute' => ':count:optional-space分钟', 'min' => ':count:optional-space分钟', 'second' => ':count:optional-space秒', - 'a_second' => '{1}几秒|]1,Inf[:count:optional-space秒', + 'a_second' => '{1}几秒|[-Inf,Inf]:count:optional-space秒', 's' => ':count:optional-space秒', 'ago' => ':time前', 'from_now' => ':time后', @@ -63,22 +63,15 @@ return [ 'lastWeek' => '[上]ddddLT', 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'd': - case 'D': - case 'DDD': - return $number.'日'; - case 'M': - return $number.'月'; - case 'w': - case 'W': - return $number.'周'; - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'd', 'D', 'DDD' => $number.'日', + 'M' => $number.'月', + 'w', 'W' => $number.'周', + default => $number, + }; }, - 'meridiem' => function ($hour, $minute) { + 'meridiem' => static function ($hour, $minute) { $time = $hour * 100 + $minute; if ($time < 600) { return '凌晨'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php index a27b61093..e34db01c1 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zh_Hant.php @@ -39,7 +39,7 @@ return [ 'minute' => ':count:optional-space分鐘', 'min' => ':count:optional-space分鐘', 'second' => ':count:optional-space秒', - 'a_second' => '{1}幾秒|]1,Inf[:count:optional-space秒', + 'a_second' => '{1}幾秒|[-Inf,Inf]:count:optional-space秒', 's' => ':count:optional-space秒', 'ago' => ':time前', 'from_now' => ':time後', @@ -65,22 +65,15 @@ return [ 'lastWeek' => '[上]dddd LT', 'sameElse' => 'L', ], - 'ordinal' => function ($number, $period) { - switch ($period) { - case 'd': - case 'D': - case 'DDD': - return $number.'日'; - case 'M': - return $number.'月'; - case 'w': - case 'W': - return $number.'周'; - default: - return $number; - } + 'ordinal' => static function ($number, $period) { + return match ($period) { + 'd', 'D', 'DDD' => $number.'日', + 'M' => $number.'月', + 'w', 'W' => $number.'周', + default => $number, + }; }, - 'meridiem' => function ($hour, $minute) { + 'meridiem' => static function ($hour, $minute) { $time = $hour * 100 + $minute; if ($time < 600) { return '凌晨'; diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php index 6bfb72f0a..b1e8bc0b5 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Lang/zu_ZA.php @@ -22,6 +22,7 @@ return array_replace_recursive(require __DIR__.'/en.php', [ 'weekdays' => ['iSonto', 'uMsombuluko', 'uLwesibili', 'uLwesithathu', 'uLwesine', 'uLwesihlanu', 'uMgqibelo'], 'weekdays_short' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], 'weekdays_min' => ['Son', 'Mso', 'Bil', 'Tha', 'Sin', 'Hla', 'Mgq'], + 'first_day_of_week' => 0, 'day_of_first_week_of_year' => 1, 'year' => 'kweminyaka engu-:count', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php index 1fb5bafdc..6197e8b00 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Language.php @@ -1,5 +1,7 @@ names) { - $this->names = static::all()[$this->code] ?? [ - 'isoName' => $this->code, - 'nativeName' => $this->code, - ]; - } + $this->names ??= static::all()[$this->code] ?? [ + 'isoName' => $this->code, + 'nativeName' => $this->code, + ]; return $this->names; } /** * Returns the original locale ID. - * - * @return string */ public function getId(): string { @@ -135,8 +100,6 @@ class Language implements JsonSerializable /** * Returns the code of the locale "en"/"fr". - * - * @return string */ public function getCode(): string { @@ -145,8 +108,6 @@ class Language implements JsonSerializable /** * Returns the variant code such as cyrl/latn. - * - * @return string|null */ public function getVariant(): ?string { @@ -155,8 +116,6 @@ class Language implements JsonSerializable /** * Returns the variant such as Cyrillic/Latin. - * - * @return string|null */ public function getVariantName(): ?string { @@ -173,8 +132,6 @@ class Language implements JsonSerializable /** * Returns the region part of the locale. - * - * @return string|null */ public function getRegion(): ?string { @@ -184,7 +141,8 @@ class Language implements JsonSerializable /** * Returns the region name for the current language. * - * @return string|null + * ⚠ ISO 3166-2 short name provided with no warranty, should not + * be used for any purpose to show official state names. */ public function getRegionName(): ?string { @@ -193,24 +151,18 @@ class Language implements JsonSerializable /** * Returns the long ISO language name. - * - * @return string */ public function getFullIsoName(): string { - if (!$this->isoName) { - $this->isoName = $this->getNames()['isoName']; - } + $this->isoName ??= $this->getNames()['isoName']; return $this->isoName; } /** * Set the ISO language name. - * - * @param string $isoName */ - public function setIsoName(string $isoName): self + public function setIsoName(string $isoName): static { $this->isoName = $isoName; @@ -219,24 +171,18 @@ class Language implements JsonSerializable /** * Return the full name of the language in this language. - * - * @return string */ public function getFullNativeName(): string { - if (!$this->nativeName) { - $this->nativeName = $this->getNames()['nativeName']; - } + $this->nativeName ??= $this->getNames()['nativeName']; return $this->nativeName; } /** * Set the name of the language in this language. - * - * @param string $nativeName */ - public function setNativeName(string $nativeName): self + public function setNativeName(string $nativeName): static { $this->nativeName = $nativeName; @@ -245,8 +191,6 @@ class Language implements JsonSerializable /** * Returns the short ISO language name. - * - * @return string */ public function getIsoName(): string { @@ -257,8 +201,6 @@ class Language implements JsonSerializable /** * Get the short name of the language in this language. - * - * @return string */ public function getNativeName(): string { @@ -269,10 +211,8 @@ class Language implements JsonSerializable /** * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. - * - * @return string */ - public function getIsoDescription() + public function getIsoDescription(): string { $region = $this->getRegionName(); $variant = $this->getVariantName(); @@ -282,10 +222,8 @@ class Language implements JsonSerializable /** * Get a string with short native name, region in parentheses if applicable, variant in parentheses if applicable. - * - * @return string */ - public function getNativeDescription() + public function getNativeDescription(): string { $region = $this->getRegionName(); $variant = $this->getVariantName(); @@ -295,10 +233,8 @@ class Language implements JsonSerializable /** * Get a string with long ISO name, region in parentheses if applicable, variant in parentheses if applicable. - * - * @return string */ - public function getFullIsoDescription() + public function getFullIsoDescription(): string { $region = $this->getRegionName(); $variant = $this->getVariantName(); @@ -308,10 +244,8 @@ class Language implements JsonSerializable /** * Get a string with long native name, region in parentheses if applicable, variant in parentheses if applicable. - * - * @return string */ - public function getFullNativeDescription() + public function getFullNativeDescription(): string { $region = $this->getRegionName(); $variant = $this->getVariantName(); @@ -321,21 +255,16 @@ class Language implements JsonSerializable /** * Returns the original locale ID. - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->getId(); } /** * Get a string with short ISO name, region in parentheses if applicable, variant in parentheses if applicable. - * - * @return string */ - #[ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): string { return $this->getIsoDescription(); } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php index 84e241e3e..76fc24a15 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Laravel/ServiceProvider.php @@ -1,5 +1,7 @@ appGetter = $appGetter; @@ -40,9 +45,15 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider $this->localeGetter = $localeGetter; } + public function setFallbackLocaleGetter(?callable $fallbackLocaleGetter): void + { + $this->fallbackLocaleGetter = $fallbackLocaleGetter; + } + public function boot() { $this->updateLocale(); + $this->updateFallbackLocale(); if (!$this->app->bound('events')) { return; @@ -79,7 +90,34 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider try { $root = Date::getFacadeRoot(); $root->setLocale($locale); - } catch (Throwable $e) { + } catch (Throwable) { + // Non Carbon class in use in Date facade + } + } + } + + public function updateFallbackLocale() + { + $locale = $this->getFallbackLocale(); + + if ($locale === null) { + return; + } + + Carbon::setFallbackLocale($locale); + CarbonImmutable::setFallbackLocale($locale); + CarbonPeriod::setFallbackLocale($locale); + CarbonInterval::setFallbackLocale($locale); + + if (class_exists(IlluminateCarbon::class) && method_exists(IlluminateCarbon::class, 'setFallbackLocale')) { + IlluminateCarbon::setFallbackLocale($locale); + } + + if (class_exists(Date::class)) { + try { + $root = Date::getFacadeRoot(); + $root->setFallbackLocale($locale); + } catch (Throwable) { // @codeCoverageIgnore // Non Carbon class in use in Date facade } } @@ -104,6 +142,19 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider return $app ? $app->getLocale() : null; } + protected function getFallbackLocale() + { + if ($this->fallbackLocaleGetter) { + return ($this->fallbackLocaleGetter)(); + } + + $app = $this->getApp(); + + return $app && method_exists($app, 'getFallbackLocale') + ? $app->getFallbackLocale() + : $this->getGlobalApp('translator')?->getFallback(); + } + protected function getApp() { if ($this->appGetter) { diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php index 5b5d9a1e7..91a2ac8db 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/languages.php @@ -1,5 +1,7 @@ 'Slovenčina, Slovenský Jazyk', ], 'sl' => [ - 'isoName' => 'Slovene', + 'isoName' => 'Slovenian', 'nativeName' => 'Slovenski Jezik, Slovenščina', ], 'so' => [ diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php index 8ab8a9e3a..f24101516 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/List/regions.php @@ -1,5 +1,7 @@ 'Andorra', @@ -41,7 +68,7 @@ return [ 'BL' => 'Saint Barthélemy', 'BM' => 'Bermuda', 'BN' => 'Brunei Darussalam', - 'BO' => 'Bolivia (Plurinational State of)', + 'BO' => 'Bolivia', 'BQ' => 'Bonaire, Sint Eustatius and Saba', 'BR' => 'Brazil', 'BS' => 'Bahamas', @@ -84,8 +111,8 @@ return [ 'ET' => 'Ethiopia', 'FI' => 'Finland', 'FJ' => 'Fiji', - 'FK' => 'Falkland Islands (Malvinas)', - 'FM' => 'Micronesia (Federated States of)', + 'FK' => 'Falkland Islands', + 'FM' => 'Micronesia', 'FO' => 'Faroe Islands', 'FR' => 'France', 'GA' => 'Gabon', @@ -120,7 +147,7 @@ return [ 'IN' => 'India', 'IO' => 'British Indian Ocean Territory', 'IQ' => 'Iraq', - 'IR' => 'Iran (Islamic Republic of)', + 'IR' => 'Iran', 'IS' => 'Iceland', 'IT' => 'Italy', 'JE' => 'Jersey', @@ -151,12 +178,12 @@ return [ 'LY' => 'Libya', 'MA' => 'Morocco', 'MC' => 'Monaco', - 'MD' => 'Moldova, Republic of', + 'MD' => 'Moldova', 'ME' => 'Montenegro', 'MF' => 'Saint Martin (French part)', 'MG' => 'Madagascar', 'MH' => 'Marshall Islands', - 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'MK' => 'North Macedonia', 'ML' => 'Mali', 'MM' => 'Myanmar', 'MN' => 'Mongolia', @@ -195,7 +222,7 @@ return [ 'PM' => 'Saint Pierre and Miquelon', 'PN' => 'Pitcairn', 'PR' => 'Puerto Rico', - 'PS' => 'Palestine, State of', + 'PS' => 'Palestine', 'PT' => 'Portugal', 'PW' => 'Palau', 'PY' => 'Paraguay', @@ -240,8 +267,8 @@ return [ 'TR' => 'Turkey', 'TT' => 'Trinidad and Tobago', 'TV' => 'Tuvalu', - 'TW' => 'Taiwan, Province of China', - 'TZ' => 'Tanzania, United Republic of', + 'TW' => 'Taiwan', + 'TZ' => 'Tanzania', 'UA' => 'Ukraine', 'UG' => 'Uganda', 'UM' => 'United States Minor Outlying Islands', @@ -250,7 +277,7 @@ return [ 'UZ' => 'Uzbekistan', 'VA' => 'Holy See', 'VC' => 'Saint Vincent and the Grenadines', - 'VE' => 'Venezuela (Bolivarian Republic of)', + 'VE' => 'Venezuela', 'VG' => 'Virgin Islands (British)', 'VI' => 'Virgin Islands (U.S.)', 'VN' => 'Viet Nam', diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php index c05480876..64b5d9768 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/MessageFormatter/MessageFormatterMapper.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidFormatException; + +enum Month: int +{ + // Using constants is only safe starting from PHP 8.2 + case January = 1; // CarbonInterface::JANUARY + case February = 2; // CarbonInterface::FEBRUARY + case March = 3; // CarbonInterface::MARCH + case April = 4; // CarbonInterface::APRIL + case May = 5; // CarbonInterface::MAY + case June = 6; // CarbonInterface::JUNE + case July = 7; // CarbonInterface::JULY + case August = 8; // CarbonInterface::AUGUST + case September = 9; // CarbonInterface::SEPTEMBER + case October = 10; // CarbonInterface::OCTOBER + case November = 11; // CarbonInterface::NOVEMBER + case December = 12; // CarbonInterface::DECEMBER + + public static function int(self|int|null $value): ?int + { + return $value instanceof self ? $value->value : $value; + } + + public static function fromNumber(int $number): self + { + $month = $number % CarbonInterface::MONTHS_PER_YEAR; + + return self::from($month + ($month < 1 ? CarbonInterface::MONTHS_PER_YEAR : 0)); + } + + public static function fromName(string $name, ?string $locale = null): self + { + try { + return self::from(CarbonImmutable::parseFromLocale("$name 1", $locale)->month); + } catch (InvalidFormatException $exception) { + // Possibly current language expect a dot after short name, but it's missing + if ($locale !== null && !mb_strlen($name) < 4 && !str_ends_with($name, '.')) { + try { + return self::from(CarbonImmutable::parseFromLocale("$name. 1", $locale)->month); + } catch (InvalidFormatException $e) { + // Throw previous error + } + } + + throw $exception; + } + } + + public function ofTheYear(CarbonImmutable|int|null $now = null): CarbonImmutable + { + if (\is_int($now)) { + return CarbonImmutable::create($now, $this->value); + } + + $modifier = $this->name.' 1st'; + + return $now?->modify($modifier) ?? new CarbonImmutable($modifier); + } + + public function locale(string $locale, ?CarbonImmutable $now = null): CarbonImmutable + { + return $this->ofTheYear($now)->locale($locale); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/OverflowMode.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/OverflowMode.php new file mode 100644 index 000000000..5157d77c8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/OverflowMode.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +enum OverflowMode +{ + case AnchorDay; + case NoOverflow; + case Overflow; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php deleted file mode 100644 index fde67b36a..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/AbstractMacro.php +++ /dev/null @@ -1,286 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -use Closure; -use InvalidArgumentException; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter as AdapterReflectionParameter; -use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType as AdapterReflectionType; -use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass; -use PHPStan\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction; -use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter; -use PHPStan\Reflection\Php\BuiltinMethodReflection; -use PHPStan\TrinaryLogic; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; -use ReflectionParameter; -use ReflectionType; -use stdClass; -use Throwable; - -abstract class AbstractMacro implements BuiltinMethodReflection -{ - /** - * The reflection function/method. - * - * @var ReflectionFunction|ReflectionMethod - */ - protected $reflectionFunction; - - /** - * The class name. - * - * @var class-string - */ - private $className; - - /** - * The method name. - * - * @var string - */ - private $methodName; - - /** - * The parameters. - * - * @var ReflectionParameter[] - */ - private $parameters; - - /** - * The is static. - * - * @var bool - */ - private $static = false; - - /** - * Macro constructor. - * - * @param class-string $className - * @param string $methodName - * @param callable $macro - */ - public function __construct(string $className, string $methodName, $macro) - { - $this->className = $className; - $this->methodName = $methodName; - $rawReflectionFunction = \is_array($macro) - ? new ReflectionMethod($macro[0], $macro[1]) - : new ReflectionFunction($macro); - $this->reflectionFunction = self::hasModernParser() - ? $this->getReflectionFunction($macro) - : $rawReflectionFunction; // @codeCoverageIgnore - $this->parameters = array_map( - function ($parameter) { - if ($parameter instanceof BetterReflectionParameter) { - return new AdapterReflectionParameter($parameter); - } - - return $parameter; // @codeCoverageIgnore - }, - $this->reflectionFunction->getParameters() - ); - - if ($rawReflectionFunction->isClosure()) { - try { - $closure = $rawReflectionFunction->getClosure(); - $boundClosure = Closure::bind($closure, new stdClass()); - $this->static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null); - } catch (Throwable $e) { - $this->static = true; - } - } - } - - private function getReflectionFunction($spec) - { - if (\is_array($spec) && \count($spec) === 2 && \is_string($spec[1])) { - \assert($spec[1] !== ''); - - if (\is_object($spec[0])) { - return BetterReflectionClass::createFromInstance($spec[0]) - ->getMethod($spec[1]); - } - - return BetterReflectionClass::createFromName($spec[0]) - ->getMethod($spec[1]); - } - - if (\is_string($spec)) { - return BetterReflectionFunction::createFromName($spec); - } - - if ($spec instanceof Closure) { - return BetterReflectionFunction::createFromClosure($spec); - } - - throw new InvalidArgumentException('Could not create reflection from the spec given'); // @codeCoverageIgnore - } - - /** - * {@inheritdoc} - */ - public function getDeclaringClass(): ReflectionClass - { - return new ReflectionClass($this->className); - } - - /** - * {@inheritdoc} - */ - public function isPrivate(): bool - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isPublic(): bool - { - return true; - } - - /** - * {@inheritdoc} - */ - public function isFinal(): bool - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isInternal(): bool - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isAbstract(): bool - { - return false; - } - - /** - * {@inheritdoc} - */ - public function isStatic(): bool - { - return $this->static; - } - - /** - * {@inheritdoc} - */ - public function getDocComment(): ?string - { - return $this->reflectionFunction->getDocComment() ?: null; - } - - /** - * {@inheritdoc} - */ - public function getName(): string - { - return $this->methodName; - } - - /** - * {@inheritdoc} - */ - public function getParameters(): array - { - return $this->parameters; - } - - /** - * {@inheritdoc} - */ - public function getReturnType(): ?ReflectionType - { - $type = $this->reflectionFunction->getReturnType(); - - if ($type instanceof ReflectionType) { - return $type; // @codeCoverageIgnore - } - - return self::adaptType($type); - } - - /** - * {@inheritdoc} - */ - public function isDeprecated(): TrinaryLogic - { - return TrinaryLogic::createFromBoolean( - $this->reflectionFunction->isDeprecated() || - preg_match('/@deprecated/i', $this->getDocComment() ?: '') - ); - } - - /** - * {@inheritdoc} - */ - public function isVariadic(): bool - { - return $this->reflectionFunction->isVariadic(); - } - - /** - * {@inheritdoc} - */ - public function getPrototype(): BuiltinMethodReflection - { - return $this; - } - - public function getTentativeReturnType(): ?ReflectionType - { - return null; - } - - public function returnsByReference(): TrinaryLogic - { - return TrinaryLogic::createNo(); - } - - private static function adaptType($type) - { - $method = method_exists(AdapterReflectionType::class, 'fromTypeOrNull') - ? 'fromTypeOrNull' - : 'fromReturnTypeOrNull'; // @codeCoverageIgnore - - return AdapterReflectionType::$method($type); - } - - private static function hasModernParser(): bool - { - static $modernParser = null; - - if ($modernParser !== null) { - return $modernParser; - } - - $modernParser = method_exists(AdapterReflectionType::class, 'fromTypeOrNull'); - - return $modernParser; - } -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php deleted file mode 100644 index de3e51f6a..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/Macro.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -use PHPStan\BetterReflection\Reflection\Adapter; -use PHPStan\Reflection\Php\BuiltinMethodReflection; -use ReflectionMethod; - -$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getReflection'); - -require $method->hasReturnType() && $method->getReturnType()->getName() === Adapter\ReflectionMethod::class - ? __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroStatic.php' - : __DIR__.'/../../../lazy/Carbon/PHPStan/AbstractMacroBuiltin.php'; - -$method = new ReflectionMethod(BuiltinMethodReflection::class, 'getFileName'); - -require $method->hasReturnType() - ? __DIR__.'/../../../lazy/Carbon/PHPStan/MacroStrongType.php' - : __DIR__.'/../../../lazy/Carbon/PHPStan/MacroWeakType.php'; - -final class Macro extends LazyMacro -{ -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php index 2cd6fce54..7327586a6 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroExtension.php @@ -1,5 +1,7 @@ scanner = new MacroScanner($reflectionProvider); - $this->methodReflectionFactory = $methodReflectionFactory; + $this->reflectionProvider = $reflectionProvider; + $this->closureTypeFactory = $closureTypeFactory; } /** @@ -55,7 +63,17 @@ final class MacroExtension implements MethodsClassReflectionExtension */ public function hasMethod(ClassReflection $classReflection, string $methodName): bool { - return $this->scanner->hasMethod($classReflection->getName(), $methodName); + if ( + $classReflection->getName() !== CarbonInterface::class && + !$classReflection->isSubclassOf(CarbonInterface::class) + ) { + return false; + } + + $className = $classReflection->getName(); + + return \is_callable([$className, 'hasMacro']) && + $className::hasMacro($methodName); } /** @@ -63,26 +81,57 @@ final class MacroExtension implements MethodsClassReflectionExtension */ public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection { - $builtinMacro = $this->scanner->getMethod($classReflection->getName(), $methodName); - $supportAssertions = class_exists(Assertions::class); + $macros = FactoryImmutable::getDefaultInstance()->getSettings()['macros'] ?? []; + $macro = $macros[$methodName] ?? throw new InvalidArgumentException("Macro '$methodName' not found"); + $static = false; + $final = false; + $deprecated = false; + $docComment = null; - return $this->methodReflectionFactory->create( + if (\is_array($macro) && \count($macro) === 2 && \is_string($macro[1])) { + \assert($macro[1] !== ''); + + $reflection = new ReflectionMethod($macro[0], $macro[1]); + $closure = \is_object($macro[0]) ? $reflection->getClosure($macro[0]) : $reflection->getClosure(); + + $static = $reflection->isStatic(); + $final = $reflection->isFinal(); + $deprecated = $reflection->isDeprecated(); + $docComment = $reflection->getDocComment() ?: null; + } elseif (\is_string($macro)) { + $reflection = new ReflectionFunction($macro); + $closure = $reflection->getClosure(); + $deprecated = $reflection->isDeprecated(); + $docComment = $reflection->getDocComment() ?: null; + } elseif ($macro instanceof Closure) { + $closure = $macro; + + try { + $boundClosure = Closure::bind($closure, new stdClass()); + $static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null); + } catch (Throwable) { + $static = true; + } + + $reflection = new ReflectionFunction($macro); + $deprecated = $reflection->isDeprecated(); + $docComment = $reflection->getDocComment() ?: null; + } + + if (!isset($closure)) { + throw new InvalidArgumentException('Could not create reflection from the spec given'); // @codeCoverageIgnore + } + + $closureType = $this->closureTypeFactory->fromClosureObject($closure); + + return new MacroMethodReflection( $classReflection, - null, - $builtinMacro, - $classReflection->getActiveTemplateTypeMap(), - [], - TypehintHelper::decideTypeFromReflection($builtinMacro->getReturnType()), - null, - null, - $builtinMacro->isDeprecated()->yes(), - $builtinMacro->isInternal(), - $builtinMacro->isFinal(), - $supportAssertions ? null : $builtinMacro->getDocComment(), - $supportAssertions ? Assertions::createEmpty() : null, - null, - $builtinMacro->getDocComment(), - [] + $methodName, + $closureType, + $static, + $final, + $deprecated, + $docComment, ); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroMethodReflection.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroMethodReflection.php new file mode 100644 index 000000000..b710f5492 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroMethodReflection.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\PHPStan; + +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParametersAcceptor; +use PHPStan\TrinaryLogic; +use PHPStan\Type\Type; + +use function preg_match; + +class MacroMethodReflection implements MethodReflection +{ + private ClassReflection $declaringClass; + private string $methodName; + private ParametersAcceptor $macroClosureType; + private bool $static; + private bool $final; + private bool $deprecated; + private ?string $docComment; + + public function __construct( + ClassReflection $declaringClass, + string $methodName, + ParametersAcceptor $macroClosureType, + bool $static, + bool $final, + bool $deprecated, + ?string $docComment + ) { + $this->declaringClass = $declaringClass; + $this->methodName = $methodName; + $this->macroClosureType = $macroClosureType; + $this->static = $static; + $this->final = $final; + $this->deprecated = $deprecated; + $this->docComment = $docComment; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->declaringClass; + } + + public function isStatic(): bool + { + return $this->static; + } + + public function isPrivate(): bool + { + return false; + } + + public function isPublic(): bool + { + return true; + } + + public function getDocComment(): ?string + { + return $this->docComment; + } + + public function getName(): string + { + return $this->methodName; + } + + public function getPrototype(): \PHPStan\Reflection\ClassMemberReflection + { + return $this; + } + + public function getVariants(): array + { + return [$this->macroClosureType]; + } + + public function isDeprecated(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean( + $this->deprecated || + preg_match('/@deprecated/i', $this->getDocComment() ?: '') + ); + } + + public function getDeprecatedDescription(): ?string + { + return null; + } + + public function isFinal(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->final); + } + + public function isInternal(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getThrowType(): ?Type + { + return null; + } + + public function hasSideEffects(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php deleted file mode 100644 index eb8957d4d..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/PHPStan/MacroScanner.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\PHPStan; - -use Carbon\CarbonInterface; -use PHPStan\Reflection\ReflectionProvider; -use ReflectionClass; -use ReflectionException; - -final class MacroScanner -{ - /** - * @var \PHPStan\Reflection\ReflectionProvider - */ - private $reflectionProvider; - - /** - * MacroScanner constructor. - * - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - */ - public function __construct(ReflectionProvider $reflectionProvider) - { - $this->reflectionProvider = $reflectionProvider; - } - - /** - * Return true if the given pair class-method is a Carbon macro. - * - * @param class-string $className - * @param string $methodName - * - * @return bool - */ - public function hasMethod(string $className, string $methodName): bool - { - $classReflection = $this->reflectionProvider->getClass($className); - - if ( - $classReflection->getName() !== CarbonInterface::class && - !$classReflection->isSubclassOf(CarbonInterface::class) - ) { - return false; - } - - return \is_callable([$className, 'hasMacro']) && - $className::hasMacro($methodName); - } - - /** - * Return the Macro for a given pair class-method. - * - * @param class-string $className - * @param string $methodName - * - * @throws ReflectionException - * - * @return Macro - */ - public function getMethod(string $className, string $methodName): Macro - { - $reflectionClass = new ReflectionClass($className); - $property = $reflectionClass->getProperty('globalMacros'); - - $property->setAccessible(true); - $macro = $property->getValue()[$methodName]; - - return new Macro( - $className, - $methodName, - $macro - ); - } -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php index 71bbb7230..b21b9c54e 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Boundaries.php @@ -1,5 +1,7 @@ startOfWeek(Carbon::SUNDAY) . "\n"; * ``` * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week * * @return static */ - public function startOfWeek($weekStartsAt = null) + public function startOfWeek(WeekDay|int|null $weekStartsAt = null): static { - return $this->subDays((7 + $this->dayOfWeek - ($weekStartsAt ?? $this->firstWeekDay)) % 7)->startOfDay(); + return $this + ->subDays( + (static::DAYS_PER_WEEK + $this->dayOfWeek - (WeekDay::int($weekStartsAt) ?? $this->firstWeekDay)) % + static::DAYS_PER_WEEK, + ) + ->startOfDay(); } /** @@ -286,13 +295,18 @@ trait Boundaries * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; * ``` * - * @param int $weekEndsAt optional start allow you to specify the day of week to use to end the week + * @param WeekDay|int|null $weekEndsAt optional end allow you to specify the day of week to use to end the week * * @return static */ - public function endOfWeek($weekEndsAt = null) + public function endOfWeek(WeekDay|int|null $weekEndsAt = null): static { - return $this->addDays((7 - $this->dayOfWeek + ($weekEndsAt ?? $this->lastWeekDay)) % 7)->endOfDay(); + return $this + ->addDays( + (static::DAYS_PER_WEEK - $this->dayOfWeek + (WeekDay::int($weekEndsAt) ?? $this->lastWeekDay)) % + static::DAYS_PER_WEEK, + ) + ->endOfDay(); } /** @@ -302,10 +316,8 @@ trait Boundaries * ``` * echo Carbon::parse('2018-07-25 12:45:16')->startOfHour(); * ``` - * - * @return static */ - public function startOfHour() + public function startOfHour(): static { return $this->setTime($this->hour, 0, 0, 0); } @@ -317,10 +329,8 @@ trait Boundaries * ``` * echo Carbon::parse('2018-07-25 12:45:16')->endOfHour(); * ``` - * - * @return static */ - public function endOfHour() + public function endOfHour(): static { return $this->setTime($this->hour, static::MINUTES_PER_HOUR - 1, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); } @@ -332,10 +342,8 @@ trait Boundaries * ``` * echo Carbon::parse('2018-07-25 12:45:16')->startOfMinute(); * ``` - * - * @return static */ - public function startOfMinute() + public function startOfMinute(): static { return $this->setTime($this->hour, $this->minute, 0, 0); } @@ -347,10 +355,8 @@ trait Boundaries * ``` * echo Carbon::parse('2018-07-25 12:45:16')->endOfMinute(); * ``` - * - * @return static */ - public function endOfMinute() + public function endOfMinute(): static { return $this->setTime($this->hour, $this->minute, static::SECONDS_PER_MINUTE - 1, static::MICROSECONDS_PER_SECOND - 1); } @@ -364,10 +370,8 @@ trait Boundaries * ->startOfSecond() * ->format('H:i:s.u'); * ``` - * - * @return static */ - public function startOfSecond() + public function startOfSecond(): static { return $this->setTime($this->hour, $this->minute, $this->second, 0); } @@ -381,32 +385,59 @@ trait Boundaries * ->endOfSecond() * ->format('H:i:s.u'); * ``` - * - * @return static */ - public function endOfSecond() + public function endOfSecond(): static { return $this->setTime($this->hour, $this->minute, $this->second, static::MICROSECONDS_PER_SECOND - 1); } + /** + * Modify to start of current millisecond, microseconds such as 12345 become 123000 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function startOfMillisecond(): static + { + $millisecond = (int) floor($this->micro / 1000); + + return $this->setTime($this->hour, $this->minute, $this->second, $millisecond * 1000); + } + + /** + * Modify to end of current millisecond, microseconds such as 12345 become 123999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function endOfMillisecond(): static + { + $millisecond = (int) floor($this->micro / 1000); + + return $this->setTime($this->hour, $this->minute, $this->second, $millisecond * 1000 + 999); + } + /** * Modify to start of current given unit. * * @example * ``` * echo Carbon::parse('2018-07-25 12:45:16.334455') - * ->startOf('month') - * ->endOf('week', Carbon::FRIDAY); + * ->startOf(Unit::Month) + * ->endOf(Unit::Week, Carbon::FRIDAY); * ``` - * - * @param string $unit - * @param array $params - * - * @return static */ - public function startOf($unit, ...$params) + public function startOf(Unit|string $unit, mixed ...$params): static { - $ucfUnit = ucfirst(static::singularUnit($unit)); + $ucfUnit = ucfirst($unit instanceof Unit ? $unit->value : static::singularUnit($unit)); $method = "startOf$ucfUnit"; if (!method_exists($this, $method)) { throw new UnknownUnitException($unit); @@ -421,18 +452,13 @@ trait Boundaries * @example * ``` * echo Carbon::parse('2018-07-25 12:45:16.334455') - * ->startOf('month') - * ->endOf('week', Carbon::FRIDAY); + * ->startOf(Unit::Month) + * ->endOf(Unit::Week, Carbon::FRIDAY); * ``` - * - * @param string $unit - * @param array $params - * - * @return static */ - public function endOf($unit, ...$params) + public function endOf(Unit|string $unit, mixed ...$params): static { - $ucfUnit = ucfirst(static::singularUnit($unit)); + $ucfUnit = ucfirst($unit instanceof Unit ? $unit->value : static::singularUnit($unit)); $method = "endOf$ucfUnit"; if (!method_exists($this, $method)) { throw new UnknownUnitException($unit); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php index 5f7c7c011..4c00e42cc 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Cast.php @@ -1,5 +1,7 @@ $className The $className::instance() method will be called to cast the current object. + * + * @return T */ - public function cast(string $className) + public function cast(string $className): mixed { if (!method_exists($className, 'instance')) { if (is_a($className, DateTimeInterface::class, true)) { - return new $className($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + return $className::createFromFormat('U.u', $this->rawFormat('U.u')) + ->setTimezone($this->getTimezone()); } throw new InvalidCastException("$className has not the instance() method needed to cast the date."); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php index f6261d882..595aa0ca2 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Comparison.php @@ -1,5 +1,7 @@ eq('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see equalTo() - * - * @return bool */ - public function eq($date): bool + public function eq(DateTimeInterface|string $date): bool { return $this->equalTo($date); } @@ -68,16 +74,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->equalTo(Carbon::parse('2018-07-25 12:45:16')); // true * Carbon::parse('2018-07-25 12:45:16')->equalTo('2018-07-25 12:45:17'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function equalTo($date): bool + public function equalTo(DateTimeInterface|string $date): bool { - $this->discourageNull($date); - $this->discourageBoolean($date); - return $this == $this->resolveCarbon($date); } @@ -91,13 +90,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->ne('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see notEqualTo() - * - * @return bool */ - public function ne($date): bool + public function ne(DateTimeInterface|string $date): bool { return $this->notEqualTo($date); } @@ -111,12 +106,8 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->notEqualTo(Carbon::parse('2018-07-25 12:45:16')); // false * Carbon::parse('2018-07-25 12:45:16')->notEqualTo('2018-07-25 12:45:17'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function notEqualTo($date): bool + public function notEqualTo(DateTimeInterface|string $date): bool { return !$this->equalTo($date); } @@ -131,13 +122,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->gt('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see greaterThan() - * - * @return bool */ - public function gt($date): bool + public function gt(DateTimeInterface|string $date): bool { return $this->greaterThan($date); } @@ -151,16 +138,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:16'); // false * Carbon::parse('2018-07-25 12:45:16')->greaterThan('2018-07-25 12:45:17'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function greaterThan($date): bool + public function greaterThan(DateTimeInterface|string $date): bool { - $this->discourageNull($date); - $this->discourageBoolean($date); - return $this > $this->resolveCarbon($date); } @@ -174,13 +154,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->isAfter('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see greaterThan() - * - * @return bool */ - public function isAfter($date): bool + public function isAfter(DateTimeInterface|string $date): bool { return $this->greaterThan($date); } @@ -195,13 +171,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->gte('2018-07-25 12:45:17'); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see greaterThanOrEqualTo() - * - * @return bool */ - public function gte($date): bool + public function gte(DateTimeInterface|string $date): bool { return $this->greaterThanOrEqualTo($date); } @@ -215,16 +187,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:16'); // true * Carbon::parse('2018-07-25 12:45:16')->greaterThanOrEqualTo('2018-07-25 12:45:17'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function greaterThanOrEqualTo($date): bool + public function greaterThanOrEqualTo(DateTimeInterface|string $date): bool { - $this->discourageNull($date); - $this->discourageBoolean($date); - return $this >= $this->resolveCarbon($date); } @@ -238,13 +203,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->lt('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see lessThan() - * - * @return bool */ - public function lt($date): bool + public function lt(DateTimeInterface|string $date): bool { return $this->lessThan($date); } @@ -258,16 +219,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:16'); // false * Carbon::parse('2018-07-25 12:45:16')->lessThan('2018-07-25 12:45:17'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function lessThan($date): bool + public function lessThan(DateTimeInterface|string $date): bool { - $this->discourageNull($date); - $this->discourageBoolean($date); - return $this < $this->resolveCarbon($date); } @@ -281,13 +235,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->isBefore('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see lessThan() - * - * @return bool */ - public function isBefore($date): bool + public function isBefore(DateTimeInterface|string $date): bool { return $this->lessThan($date); } @@ -302,13 +252,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->lte('2018-07-25 12:45:17'); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * * @see lessThanOrEqualTo() - * - * @return bool */ - public function lte($date): bool + public function lte(DateTimeInterface|string $date): bool { return $this->lessThanOrEqualTo($date); } @@ -322,16 +268,9 @@ trait Comparison * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:16'); // true * Carbon::parse('2018-07-25 12:45:16')->lessThanOrEqualTo('2018-07-25 12:45:17'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date - * - * @return bool */ - public function lessThanOrEqualTo($date): bool + public function lessThanOrEqualTo(DateTimeInterface|string $date): bool { - $this->discourageNull($date); - $this->discourageBoolean($date); - return $this <= $this->resolveCarbon($date); } @@ -350,13 +289,9 @@ trait Comparison * Carbon::parse('2018-07-25')->between('2018-07-25', '2018-08-01', false); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * @param bool $equal Indicates if an equal to comparison should be done - * - * @return bool + * @param bool $equal Indicates if an equal to comparison should be done */ - public function between($date1, $date2, $equal = true): bool + public function between(DateTimeInterface|string $date1, DateTimeInterface|string $date2, bool $equal = true): bool { $date1 = $this->resolveCarbon($date1); $date2 = $this->resolveCarbon($date2); @@ -381,13 +316,8 @@ trait Comparison * Carbon::parse('2018-07-25')->betweenIncluded('2018-08-01', '2018-08-20'); // false * Carbon::parse('2018-07-25')->betweenIncluded('2018-07-25', '2018-08-01'); // true * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * - * @return bool */ - public function betweenIncluded($date1, $date2): bool + public function betweenIncluded(DateTimeInterface|string $date1, DateTimeInterface|string $date2): bool { return $this->between($date1, $date2, true); } @@ -401,13 +331,8 @@ trait Comparison * Carbon::parse('2018-07-25')->betweenExcluded('2018-08-01', '2018-08-20'); // false * Carbon::parse('2018-07-25')->betweenExcluded('2018-07-25', '2018-08-01'); // false * ``` - * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * - * @return bool */ - public function betweenExcluded($date1, $date2): bool + public function betweenExcluded(DateTimeInterface|string $date1, DateTimeInterface|string $date2): bool { return $this->between($date1, $date2, false); } @@ -423,13 +348,9 @@ trait Comparison * Carbon::parse('2018-07-25')->isBetween('2018-07-25', '2018-08-01', false); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date1 - * @param \Carbon\Carbon|\DateTimeInterface|mixed $date2 - * @param bool $equal Indicates if an equal to comparison should be done - * - * @return bool + * @param bool $equal Indicates if an equal to comparison should be done */ - public function isBetween($date1, $date2, $equal = true): bool + public function isBetween(DateTimeInterface|string $date1, DateTimeInterface|string $date2, bool $equal = true): bool { return $this->between($date1, $date2, $equal); } @@ -442,10 +363,8 @@ trait Comparison * Carbon::parse('2019-07-14')->isWeekday(); // false * Carbon::parse('2019-07-15')->isWeekday(); // true * ``` - * - * @return bool */ - public function isWeekday() + public function isWeekday(): bool { return !$this->isWeekend(); } @@ -458,12 +377,14 @@ trait Comparison * Carbon::parse('2019-07-14')->isWeekend(); // true * Carbon::parse('2019-07-15')->isWeekend(); // false * ``` - * - * @return bool */ - public function isWeekend() + public function isWeekend(): bool { - return \in_array($this->dayOfWeek, static::$weekendDays, true); + return \in_array( + $this->dayOfWeek, + $this->transmitFactory(static fn () => static::getWeekendDays()), + true, + ); } /** @@ -474,12 +395,12 @@ trait Comparison * Carbon::yesterday()->isYesterday(); // true * Carbon::tomorrow()->isYesterday(); // false * ``` - * - * @return bool */ - public function isYesterday() + public function isYesterday(): bool { - return $this->toDateString() === static::yesterday($this->getTimezone())->toDateString(); + return $this->toDateString() === $this->transmitFactory( + fn () => static::yesterday($this->getTimezone())->toDateString(), + ); } /** @@ -490,10 +411,8 @@ trait Comparison * Carbon::today()->isToday(); // true * Carbon::tomorrow()->isToday(); // false * ``` - * - * @return bool */ - public function isToday() + public function isToday(): bool { return $this->toDateString() === $this->nowWithSameTz()->toDateString(); } @@ -506,12 +425,12 @@ trait Comparison * Carbon::tomorrow()->isTomorrow(); // true * Carbon::yesterday()->isTomorrow(); // false * ``` - * - * @return bool */ - public function isTomorrow() + public function isTomorrow(): bool { - return $this->toDateString() === static::tomorrow($this->getTimezone())->toDateString(); + return $this->toDateString() === $this->transmitFactory( + fn () => static::tomorrow($this->getTimezone())->toDateString(), + ); } /** @@ -522,10 +441,8 @@ trait Comparison * Carbon::now()->addHours(5)->isFuture(); // true * Carbon::now()->subHours(5)->isFuture(); // false * ``` - * - * @return bool */ - public function isFuture() + public function isFuture(): bool { return $this->greaterThan($this->nowWithSameTz()); } @@ -538,14 +455,42 @@ trait Comparison * Carbon::now()->subHours(5)->isPast(); // true * Carbon::now()->addHours(5)->isPast(); // false * ``` - * - * @return bool */ - public function isPast() + public function isPast(): bool { return $this->lessThan($this->nowWithSameTz()); } + /** + * Determines if the instance is now or in the future, ie. greater (after) than or equal to now. + * + * @example + * ``` + * Carbon::now()->isNowOrFuture(); // true + * Carbon::now()->addHours(5)->isNowOrFuture(); // true + * Carbon::now()->subHours(5)->isNowOrFuture(); // false + * ``` + */ + public function isNowOrFuture(): bool + { + return $this->greaterThanOrEqualTo($this->nowWithSameTz()); + } + + /** + * Determines if the instance is now or in the past, ie. less (before) than or equal to now. + * + * @example + * ``` + * Carbon::now()->isNowOrPast(); // true + * Carbon::now()->subHours(5)->isNowOrPast(); // true + * Carbon::now()->addHours(5)->isNowOrPast(); // false + * ``` + */ + public function isNowOrPast(): bool + { + return $this->lessThanOrEqualTo($this->nowWithSameTz()); + } + /** * Determines if the instance is a leap year. * @@ -554,10 +499,8 @@ trait Comparison * Carbon::parse('2020-01-01')->isLeapYear(); // true * Carbon::parse('2019-01-01')->isLeapYear(); // false * ``` - * - * @return bool */ - public function isLeapYear() + public function isLeapYear(): bool { return $this->rawFormat('L') === '1'; } @@ -577,12 +520,10 @@ trait Comparison * ``` * * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates - * - * @return bool */ - public function isLongYear() + public function isLongYear(): bool { - return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; + return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === static::WEEKS_PER_YEAR + 1; } /** @@ -598,10 +539,8 @@ trait Comparison * ``` * * @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates - * - * @return bool */ - public function isLongIsoYear() + public function isLongIsoYear(): bool { return static::create($this->isoWeekYear, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53; } @@ -615,12 +554,10 @@ trait Comparison * Carbon::parse('2019-06-13')->isSameAs('Y-d', Carbon::parse('2019-06-14')); // false * ``` * - * @param string $format date formats to compare. - * @param \Carbon\Carbon|\DateTimeInterface|string|null $date instance to compare with or null to use current day. - * - * @return bool + * @param string $format date formats to compare. + * @param DateTimeInterface|string $date instance to compare with or null to use current day. */ - public function isSameAs($format, $date = null) + public function isSameAs(string $format, DateTimeInterface|string $date): bool { return $this->rawFormat($format) === $this->resolveCarbon($date)->rawFormat($format); } @@ -634,19 +571,27 @@ trait Comparison * Carbon::parse('2018-12-13')->isSameUnit('year', Carbon::parse('2019-12-25')); // false * ``` * - * @param string $unit singular unit string - * @param \Carbon\Carbon|\DateTimeInterface|null $date instance to compare with or null to use current day. + * @param string $unit singular unit string + * @param DateTimeInterface|string $date instance to compare with or null to use current day. * * @throws BadComparisonUnitException * * @return bool */ - public function isSameUnit($unit, $date = null) + public function isSameUnit(string $unit, DateTimeInterface|string $date): bool { + if ($unit === /* @call isSameUnit */ 'quarter') { + $other = $this->resolveCarbon($date); + + return $other->year === $this->year && $other->quarter === $this->quarter; + } + $units = [ // @call isSameUnit 'year' => 'Y', // @call isSameUnit + 'month' => 'Y-n', + // @call isSameUnit 'week' => 'o-W', // @call isSameUnit 'day' => 'Y-m-d', @@ -657,6 +602,10 @@ trait Comparison // @call isSameUnit 'second' => 'Y-m-d H:i:s', // @call isSameUnit + 'milli' => 'Y-m-d H:i:s.v', + // @call isSameUnit + 'millisecond' => 'Y-m-d H:i:s.v', + // @call isSameUnit 'micro' => 'Y-m-d H:i:s.u', // @call isSameUnit 'microsecond' => 'Y-m-d H:i:s.u', @@ -670,7 +619,7 @@ trait Comparison return $this->resolveCarbon($date)->$unit === $this->$unit; } - if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + if ($this->isLocalStrictModeEnabled()) { throw new BadComparisonUnitException($unit); } @@ -689,12 +638,10 @@ trait Comparison * @param string $unit The unit to test. * * @throws BadMethodCallException - * - * @return bool */ - public function isCurrentUnit($unit) + public function isCurrentUnit(string $unit): bool { - return $this->{'isSame'.ucfirst($unit)}(); + return $this->{'isSame'.ucfirst($unit)}('now'); } /** @@ -708,12 +655,12 @@ trait Comparison * Carbon::parse('2019-01-12')->isSameQuarter(Carbon::parse('2018-03-01'), false); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|string|null $date The instance to compare with or null to use current day. - * @param bool $ofSameYear Check if it is the same month in the same year. + * @param DateTimeInterface|string $date The instance to compare with or null to use current day. + * @param bool $ofSameYear Check if it is the same month in the same year. * * @return bool */ - public function isSameQuarter($date = null, $ofSameYear = true) + public function isSameQuarter(DateTimeInterface|string $date, bool $ofSameYear = true): bool { $date = $this->resolveCarbon($date); @@ -731,12 +678,12 @@ trait Comparison * Carbon::parse('2019-01-12')->isSameMonth(Carbon::parse('2018-01-01'), false); // true * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use the current date. - * @param bool $ofSameYear Check if it is the same month in the same year. + * @param DateTimeInterface|string $date The instance to compare with or null to use the current date. + * @param bool $ofSameYear Check if it is the same month in the same year. * * @return bool */ - public function isSameMonth($date = null, $ofSameYear = true) + public function isSameMonth(DateTimeInterface|string $date, bool $ofSameYear = true): bool { return $this->isSameAs($ofSameYear ? 'Y-m' : 'm', $date); } @@ -752,11 +699,11 @@ trait Comparison * Carbon::parse('2019-07-17')->isDayOfWeek('Friday'); // false * ``` * - * @param int $dayOfWeek + * @param int|string $dayOfWeek * * @return bool */ - public function isDayOfWeek($dayOfWeek) + public function isDayOfWeek($dayOfWeek): bool { if (\is_string($dayOfWeek) && \defined($constant = static::class.'::'.strtoupper($dayOfWeek))) { $dayOfWeek = \constant($constant); @@ -776,13 +723,13 @@ trait Comparison * Carbon::parse('2019-06-05')->isBirthday(Carbon::parse('2001-06-06')); // false * ``` * - * @param \Carbon\Carbon|\DateTimeInterface|null $date The instance to compare with or null to use current day. + * @param DateTimeInterface|string|null $date The instance to compare with or null to use current day. * * @return bool */ - public function isBirthday($date = null) + public function isBirthday(DateTimeInterface|string|null $date = null): bool { - return $this->isSameAs('md', $date); + return $this->isSameAs('md', $date ?? 'now'); } /** @@ -796,14 +743,148 @@ trait Comparison * Carbon::parse('2019-03-31')->isLastOfMonth(); // true * Carbon::parse('2019-04-30')->isLastOfMonth(); // true * ``` - * - * @return bool */ - public function isLastOfMonth() + public function isLastOfMonth(): bool { return $this->day === $this->daysInMonth; } + /** + * Check if the instance is start of a given unit (tolerating a given interval). + * + * @example + * ``` + * // Check if a date-time is the first 15 minutes of the hour it's in + * Carbon::parse('2019-02-28 20:13:00')->isStartOfUnit(Unit::Hour, '15 minutes'); // true + * ``` + */ + public function isStartOfUnit( + Unit $unit, + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + mixed ...$params, + ): bool { + $interval ??= match ($unit) { + Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, + default => Unit::Day, + }; + + $startOfUnit = $this->avoidMutation()->startOf($unit, ...$params); + $startOfUnitDateTime = $startOfUnit->rawFormat('Y-m-d H:i:s.u'); + $maximumDateTime = $startOfUnit + ->add($interval instanceof Unit ? '1 '.$interval->value : $interval) + ->rawFormat('Y-m-d H:i:s.u'); + + if ($maximumDateTime < $startOfUnitDateTime) { + return false; + } + + return $this->rawFormat('Y-m-d H:i:s.u') < $maximumDateTime; + } + + /** + * Check if the instance is end of a given unit (tolerating a given interval). + * + * @example + * ``` + * // Check if a date-time is the last 15 minutes of the hour it's in + * Carbon::parse('2019-02-28 20:13:00')->isEndOfUnit(Unit::Hour, '15 minutes'); // false + * ``` + */ + public function isEndOfUnit( + Unit $unit, + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + mixed ...$params, + ): bool { + $interval ??= match ($unit) { + Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, + default => Unit::Day, + }; + + $endOfUnit = $this->avoidMutation()->endOf($unit, ...$params); + $endOfUnitDateTime = $endOfUnit->rawFormat('Y-m-d H:i:s.u'); + $minimumDateTime = $endOfUnit + ->sub($interval instanceof Unit ? '1 '.$interval->value : $interval) + ->rawFormat('Y-m-d H:i:s.u'); + + if ($minimumDateTime > $endOfUnitDateTime) { + return false; + } + + return $this->rawFormat('Y-m-d H:i:s.u') > $minimumDateTime; + } + + /** + * Determines if the instance is start of millisecond (first microsecond by default but interval can be customized). + */ + public function isStartOfMillisecond( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Millisecond, $interval); + } + + /** + * Determines if the instance is end of millisecond (last microsecond by default but interval can be customized). + */ + public function isEndOfMillisecond( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Millisecond, $interval); + } + + /** + * Determines if the instance is start of second (first microsecond by default but interval can be customized). + */ + public function isStartOfSecond( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Second, $interval); + } + + /** + * Determines if the instance is end of second (last microsecond by default but interval can be customized). + */ + public function isEndOfSecond( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Second, $interval); + } + + /** + * Determines if the instance is start of minute (first microsecond by default but interval can be customized). + */ + public function isStartOfMinute( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Minute, $interval); + } + + /** + * Determines if the instance is end of minute (last microsecond by default but interval can be customized). + */ + public function isEndOfMinute( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Minute, $interval); + } + + /** + * Determines if the instance is start of hour (first microsecond by default but interval can be customized). + */ + public function isStartOfHour( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Hour, $interval); + } + + /** + * Determines if the instance is end of hour (last microsecond by default but interval can be customized). + */ + public function isEndOfHour( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Hour, $interval); + } + /** * Check if the instance is start of day / midnight. * @@ -816,12 +897,47 @@ trait Comparison * Carbon::parse('2019-02-28 00:00:00.000012')->isStartOfDay(true); // false * ``` * - * @param bool $checkMicroseconds check time at microseconds precision - * - * @return bool + * @param bool $checkMicroseconds check time at microseconds precision + * @param Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval if an interval is specified it will be used as precision + * for instance with "15 minutes", it checks if current date-time + * is in the last 15 minutes of the day, with Unit::Hour, it + * checks if it's in the last hour of the day. */ - public function isStartOfDay($checkMicroseconds = false) - { + public function isStartOfDay( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|bool $checkMicroseconds = false, + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + if ($checkMicroseconds === true) { + @trigger_error( + "Since 3.8.0, it's deprecated to use \$checkMicroseconds.\n". + "It will be removed in 4.0.0.\n". + "Instead, you should use either isStartOfDay(interval: Unit::Microsecond) or isStartOfDay(interval: Unit::Second)\n". + 'And you can now use any custom interval as precision, such as isStartOfDay(interval: "15 minutes")', + \E_USER_DEPRECATED, + ); + } + + if ($interval === null && !\is_bool($checkMicroseconds)) { + $interval = $checkMicroseconds; + } + + if ($interval !== null) { + if ($interval instanceof Unit) { + $interval = '1 '.$interval->value; + } + + $date = $this->rawFormat('Y-m-d'); + $time = $this->rawFormat('H:i:s.u'); + $maximum = $this->avoidMutation()->startOfDay()->add($interval); + $maximumDate = $maximum->rawFormat('Y-m-d'); + + if ($date === $maximumDate) { + return $time < $maximum->rawFormat('H:i:s.u'); + } + + return $maximumDate > $date; + } + /* @var CarbonInterface $this */ return $checkMicroseconds ? $this->rawFormat('H:i:s.u') === '00:00:00.000000' @@ -842,18 +958,191 @@ trait Comparison * Carbon::parse('2019-02-28 23:59:59')->isEndOfDay(true); // false * ``` * - * @param bool $checkMicroseconds check time at microseconds precision - * - * @return bool + * @param bool $checkMicroseconds check time at microseconds precision + * @param Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval if an interval is specified it will be used as precision + * for instance with "15 minutes", it checks if current date-time + * is in the last 15 minutes of the day, with Unit::Hour, it + * checks if it's in the last hour of the day. */ - public function isEndOfDay($checkMicroseconds = false) - { + public function isEndOfDay( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|bool $checkMicroseconds = false, + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + if ($checkMicroseconds === true) { + @trigger_error( + "Since 3.8.0, it's deprecated to use \$checkMicroseconds.\n". + "It will be removed in 4.0.0.\n". + "Instead, you should use either isEndOfDay(interval: Unit::Microsecond) or isEndOfDay(interval: Unit::Second)\n". + 'And you can now use any custom interval as precision, such as isEndOfDay(interval: "15 minutes")', + \E_USER_DEPRECATED, + ); + } + + if ($interval === null && !\is_bool($checkMicroseconds)) { + $interval = $checkMicroseconds; + } + + if ($interval !== null) { + $date = $this->rawFormat('Y-m-d'); + $time = $this->rawFormat('H:i:s.u'); + $minimum = $this->avoidMutation() + ->endOfDay() + ->sub($interval instanceof Unit ? '1 '.$interval->value : $interval); + $minimumDate = $minimum->rawFormat('Y-m-d'); + + if ($date === $minimumDate) { + return $time > $minimum->rawFormat('H:i:s.u'); + } + + return $minimumDate < $date; + } + /* @var CarbonInterface $this */ return $checkMicroseconds ? $this->rawFormat('H:i:s.u') === '23:59:59.999999' : $this->rawFormat('H:i:s') === '23:59:59'; } + /** + * Determines if the instance is start of week (first day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->startOfWeek()->isStartOfWeek(); // true + * Carbon::parse('2024-08-31')->isStartOfWeek(); // false + * ``` + */ + public function isStartOfWeek( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + WeekDay|int|null $weekStartsAt = null, + ): bool { + return $this->isStartOfUnit(Unit::Week, $interval, $weekStartsAt); + } + + /** + * Determines if the instance is end of week (last day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->endOfWeek()->isEndOfWeek(); // true + * Carbon::parse('2024-08-31')->isEndOfWeek(); // false + * ``` + */ + public function isEndOfWeek( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + WeekDay|int|null $weekEndsAt = null, + ): bool { + return $this->isEndOfUnit(Unit::Week, $interval, $weekEndsAt); + } + + /** + * Determines if the instance is start of month (first day by default but interval can be customized). + */ + public function isStartOfMonth( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Month, $interval); + } + + /** + * Determines if the instance is end of month (last day by default but interval can be customized). + */ + public function isEndOfMonth( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Month, $interval); + } + + /** + * Determines if the instance is start of quarter (first day by default but interval can be customized). + */ + public function isStartOfQuarter( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Quarter, $interval); + } + + /** + * Determines if the instance is end of quarter (last day by default but interval can be customized). + */ + public function isEndOfQuarter( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Quarter, $interval); + } + + /** + * Determines if the instance is start of year (first day by default but interval can be customized). + */ + public function isStartOfYear( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Year, $interval); + } + + /** + * Determines if the instance is end of year (last day by default but interval can be customized). + */ + public function isEndOfYear( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Year, $interval); + } + + /** + * Determines if the instance is start of decade (first day by default but interval can be customized). + */ + public function isStartOfDecade( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Decade, $interval); + } + + /** + * Determines if the instance is end of decade (last day by default but interval can be customized). + */ + public function isEndOfDecade( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Decade, $interval); + } + + /** + * Determines if the instance is start of century (first day by default but interval can be customized). + */ + public function isStartOfCentury( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Century, $interval); + } + + /** + * Determines if the instance is end of century (last day by default but interval can be customized). + */ + public function isEndOfCentury( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Century, $interval); + } + + /** + * Determines if the instance is start of millennium (first day by default but interval can be customized). + */ + public function isStartOfMillennium( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Millennium, $interval); + } + + /** + * Determines if the instance is end of millennium (last day by default but interval can be customized). + */ + public function isEndOfMillennium( + Unit|DateInterval|Closure|CarbonConverterInterface|Duration|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Millennium, $interval); + } + /** * Check if the instance is start of day / midnight. * @@ -863,10 +1152,8 @@ trait Comparison * Carbon::parse('2019-02-28 00:00:00.999999')->isMidnight(); // true * Carbon::parse('2019-02-28 00:00:01')->isMidnight(); // false * ``` - * - * @return bool */ - public function isMidnight() + public function isMidnight(): bool { return $this->isStartOfDay(); } @@ -881,10 +1168,8 @@ trait Comparison * Carbon::parse('2019-02-28 12:00:00.999999')->isMidday(); // true * Carbon::parse('2019-02-28 12:00:01')->isMidday(); // false * ``` - * - * @return bool */ - public function isMidday() + public function isMidday(): bool { /* @var CarbonInterface $this */ return $this->rawFormat('G:i:s') === static::$midDayAt.':00:00'; @@ -898,19 +1183,10 @@ trait Comparison * Carbon::hasFormat('11:12:45', 'h:i:s'); // true * Carbon::hasFormat('13:12:45', 'h:i:s'); // false * ``` - * - * @param string $date - * @param string $format - * - * @return bool */ - public static function hasFormat($date, $format) + public static function hasFormat(string $date, string $format): bool { - // createFromFormat() is known to handle edge cases silently. - // E.g. "1975-5-1" (Y-n-j) will still be parsed correctly when "Y-m-d" is supplied as the format. - // To ensure we're really testing against our desired format, perform an additional regex validation. - - return self::matchFormatPattern((string) $date, preg_quote((string) $format, '/'), static::$regexFormats); + return FactoryImmutable::getInstance()->hasFormat($date, $format); } /** @@ -927,9 +1203,9 @@ trait Comparison * * @return bool */ - public static function hasFormatWithModifiers($date, $format): bool + public static function hasFormatWithModifiers(?string $date, string $format): bool { - return self::matchFormatPattern((string) $date, (string) $format, array_merge(static::$regexFormats, static::$regexFormatModifiers)); + return FactoryImmutable::getInstance()->hasFormatWithModifiers($date, $format); } /** @@ -941,21 +1217,20 @@ trait Comparison * Carbon::canBeCreatedFromFormat('11:12:45', 'h:i:s'); // true * Carbon::canBeCreatedFromFormat('13:12:45', 'h:i:s'); // false * ``` - * - * @param string $date - * @param string $format - * - * @return bool */ - public static function canBeCreatedFromFormat($date, $format) + public static function canBeCreatedFromFormat(?string $date, string $format): bool { + if ($date === null) { + return false; + } + try { // Try to create a DateTime object. Throws an InvalidArgumentException if the provided time string // doesn't match the format in any way. if (!static::rawCreateFromFormat($format, $date)) { return false; } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } @@ -983,29 +1258,41 @@ trait Comparison * ``` * * @param string $tester day name, month name, hour, date, etc. as string - * - * @return bool */ - public function is(string $tester) + public function is(WeekDay|Month|string $tester): bool { + if ($tester instanceof BackedEnum) { + $tester = $tester->name; + } + $tester = trim($tester); if (preg_match('/^\d+$/', $tester)) { return $this->year === (int) $tester; } - if (preg_match('/^\d{3,}-\d{1,2}$/', $tester)) { - return $this->isSameMonth(static::parse($tester)); + if (preg_match('/^(?:Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|June|Jul|July|Aug|August|Sep|September|Oct|October|Nov|November|Dec|December)$/i', $tester)) { + return $this->isSameMonth( + $this->transmitFactory(static fn () => static::parse("$tester 1st")), + false, + ); } - if (preg_match('/^\d{1,2}-\d{1,2}$/', $tester)) { - return $this->isSameDay(static::parse($this->year.'-'.$tester)); + if (preg_match('/^\d{3,}-\d{1,2}$/', $tester)) { + return $this->isSameMonth( + $this->transmitFactory(static fn () => static::parse($tester)), + ); + } + + if (preg_match('/^(\d{1,2})-(\d{1,2})$/', $tester, $match)) { + return $this->month === (int) $match[1] && $this->day === (int) $match[2]; } $modifier = preg_replace('/(\d)h$/i', '$1:00', $tester); /* @var CarbonInterface $max */ - $median = static::parse('5555-06-15 12:30:30.555555')->modify($modifier); + $median = $this->transmitFactory(static fn () => static::parse('5555-06-15 12:30:30.555555')) + ->modify($modifier); $current = $this->avoidMutation(); /* @var CarbonInterface $other */ $other = $this->avoidMutation()->modify($modifier); @@ -1028,7 +1315,7 @@ trait Comparison if (preg_match( '/^(?:january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+\d+)?$/i', - $tester + $tester, )) { return $current->startOfMonth()->eq($other->startOfMonth()); } @@ -1053,42 +1340,6 @@ trait Comparison return $current->eq($other); } - /** - * Checks if the (date)time string is in a given format with - * given list of pattern replacements. - * - * @example - * ``` - * Carbon::hasFormat('11:12:45', 'h:i:s'); // true - * Carbon::hasFormat('13:12:45', 'h:i:s'); // false - * ``` - * - * @param string $date - * @param string $format - * @param array $replacements - * - * @return bool - */ - private static function matchFormatPattern(string $date, string $format, array $replacements): bool - { - // Preg quote, but remove escaped backslashes since we'll deal with escaped characters in the format string. - $regex = str_replace('\\\\', '\\', $format); - // Replace not-escaped letters - $regex = preg_replace_callback( - '/(?endOfTime ?? false; } - - private function discourageNull($value): void - { - if ($value === null) { - @trigger_error("Since 2.61.0, it's deprecated to compare a date to null, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate null values.", \E_USER_DEPRECATED); - } - } - - private function discourageBoolean($value): void - { - if (\is_bool($value)) { - @trigger_error("Since 2.61.0, it's deprecated to compare a date to true or false, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate boolean values.", \E_USER_DEPRECATED); - } - } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php index fff8a600a..3bffa16b9 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Converter.php @@ -1,5 +1,7 @@ localFormatFunction ?: static::$formatFunction; + $function = $this->localFormatFunction + ?? $this->getFactory()->getSettings()['formatFunction'] + ?? static::$formatFunction; if (!$function) { return $this->rawFormat($format); @@ -64,12 +64,8 @@ trait Converter /** * @see https://php.net/manual/en/datetime.format.php - * - * @param string $format - * - * @return string */ - public function rawFormat($format) + public function rawFormat(string $format): string { return parent::format($format); } @@ -81,12 +77,12 @@ trait Converter * ``` * echo Carbon::now(); // Carbon instances can be cast to string * ``` - * - * @return string */ - public function __toString() + public function __toString(): string { - $format = $this->localToStringFormat ?? static::$toStringFormat; + $format = $this->localToStringFormat + ?? $this->getFactory()->getSettings()['toStringFormat'] + ?? null; return $format instanceof Closure ? $format($this) @@ -104,10 +100,8 @@ trait Converter * ``` * echo Carbon::now()->toDateString(); * ``` - * - * @return string */ - public function toDateString() + public function toDateString(): string { return $this->rawFormat('Y-m-d'); } @@ -119,10 +113,8 @@ trait Converter * ``` * echo Carbon::now()->toFormattedDateString(); * ``` - * - * @return string */ - public function toFormattedDateString() + public function toFormattedDateString(): string { return $this->rawFormat('M j, Y'); } @@ -134,8 +126,6 @@ trait Converter * ``` * echo Carbon::now()->toFormattedDayDateString(); * ``` - * - * @return string */ public function toFormattedDayDateString(): string { @@ -149,12 +139,8 @@ trait Converter * ``` * echo Carbon::now()->toTimeString(); * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toTimeString($unitPrecision = 'second') + public function toTimeString(string $unitPrecision = 'second'): string { return $this->rawFormat(static::getTimeFormatByPrecision($unitPrecision)); } @@ -166,12 +152,8 @@ trait Converter * ``` * echo Carbon::now()->toDateTimeString(); * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toDateTimeString($unitPrecision = 'second') + public function toDateTimeString(string $unitPrecision = 'second'): string { return $this->rawFormat('Y-m-d '.static::getTimeFormatByPrecision($unitPrecision)); } @@ -180,25 +162,16 @@ trait Converter * Return a format from H:i to H:i:s.u according to given unit precision. * * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" - * - * @return string */ - public static function getTimeFormatByPrecision($unitPrecision) + public static function getTimeFormatByPrecision(string $unitPrecision): string { - switch (static::singularUnit($unitPrecision)) { - case 'minute': - return 'H:i'; - case 'second': - return 'H:i:s'; - case 'm': - case 'millisecond': - return 'H:i:s.v'; - case 'µ': - case 'microsecond': - return 'H:i:s.u'; - } - - throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); + return match (static::singularUnit($unitPrecision)) { + 'minute' => 'H:i', + 'second' => 'H:i:s', + 'm', 'millisecond' => 'H:i:s.v', + 'µ', 'microsecond' => 'H:i:s.u', + default => throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'), + }; } /** @@ -210,12 +183,8 @@ trait Converter * echo "\n"; * echo Carbon::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toDateTimeLocalString($unitPrecision = 'second') + public function toDateTimeLocalString(string $unitPrecision = 'second'): string { return $this->rawFormat('Y-m-d\T'.static::getTimeFormatByPrecision($unitPrecision)); } @@ -227,10 +196,8 @@ trait Converter * ``` * echo Carbon::now()->toDayDateTimeString(); * ``` - * - * @return string */ - public function toDayDateTimeString() + public function toDayDateTimeString(): string { return $this->rawFormat('D, M j, Y g:i A'); } @@ -242,10 +209,8 @@ trait Converter * ``` * echo Carbon::now()->toAtomString(); * ``` - * - * @return string */ - public function toAtomString() + public function toAtomString(): string { return $this->rawFormat(DateTime::ATOM); } @@ -257,12 +222,10 @@ trait Converter * ``` * echo Carbon::now()->toCookieString(); * ``` - * - * @return string */ - public function toCookieString() + public function toCookieString(): string { - return $this->rawFormat(DateTime::COOKIE); + return $this->rawFormat(DateTimeInterface::COOKIE); } /** @@ -272,10 +235,8 @@ trait Converter * ``` * echo Carbon::now()->toIso8601String(); * ``` - * - * @return string */ - public function toIso8601String() + public function toIso8601String(): string { return $this->toAtomString(); } @@ -287,12 +248,10 @@ trait Converter * ``` * echo Carbon::now()->toRfc822String(); * ``` - * - * @return string */ - public function toRfc822String() + public function toRfc822String(): string { - return $this->rawFormat(DateTime::RFC822); + return $this->rawFormat(DateTimeInterface::RFC822); } /** @@ -302,12 +261,8 @@ trait Converter * ``` * echo Carbon::now()->toIso8601ZuluString(); * ``` - * - * @param string $unitPrecision - * - * @return string */ - public function toIso8601ZuluString($unitPrecision = 'second') + public function toIso8601ZuluString(string $unitPrecision = 'second'): string { return $this->avoidMutation() ->utc() @@ -321,12 +276,10 @@ trait Converter * ``` * echo Carbon::now()->toRfc850String(); * ``` - * - * @return string */ - public function toRfc850String() + public function toRfc850String(): string { - return $this->rawFormat(DateTime::RFC850); + return $this->rawFormat(DateTimeInterface::RFC850); } /** @@ -336,12 +289,10 @@ trait Converter * ``` * echo Carbon::now()->toRfc1036String(); * ``` - * - * @return string */ - public function toRfc1036String() + public function toRfc1036String(): string { - return $this->rawFormat(DateTime::RFC1036); + return $this->rawFormat(DateTimeInterface::RFC1036); } /** @@ -351,12 +302,10 @@ trait Converter * ``` * echo Carbon::now()->toRfc1123String(); * ``` - * - * @return string */ - public function toRfc1123String() + public function toRfc1123String(): string { - return $this->rawFormat(DateTime::RFC1123); + return $this->rawFormat(DateTimeInterface::RFC1123); } /** @@ -366,35 +315,24 @@ trait Converter * ``` * echo Carbon::now()->toRfc2822String(); * ``` - * - * @return string */ - public function toRfc2822String() + public function toRfc2822String(): string { - return $this->rawFormat(DateTime::RFC2822); + return $this->rawFormat(DateTimeInterface::RFC2822); } /** - * Format the instance as RFC3339 - * - * @param bool $extended + * Format the instance as RFC3339. * * @example * ``` * echo Carbon::now()->toRfc3339String() . "\n"; * echo Carbon::now()->toRfc3339String(true) . "\n"; * ``` - * - * @return string */ - public function toRfc3339String($extended = false) + public function toRfc3339String(bool $extended = false): string { - $format = DateTime::RFC3339; - if ($extended) { - $format = DateTime::RFC3339_EXTENDED; - } - - return $this->rawFormat($format); + return $this->rawFormat($extended ? DateTimeInterface::RFC3339_EXTENDED : DateTimeInterface::RFC3339); } /** @@ -404,12 +342,10 @@ trait Converter * ``` * echo Carbon::now()->toRssString(); * ``` - * - * @return string */ - public function toRssString() + public function toRssString(): string { - return $this->rawFormat(DateTime::RSS); + return $this->rawFormat(DateTimeInterface::RSS); } /** @@ -419,12 +355,10 @@ trait Converter * ``` * echo Carbon::now()->toW3cString(); * ``` - * - * @return string */ - public function toW3cString() + public function toW3cString(): string { - return $this->rawFormat(DateTime::W3C); + return $this->rawFormat(DateTimeInterface::W3C); } /** @@ -434,10 +368,8 @@ trait Converter * ``` * echo Carbon::now()->toRfc7231String(); * ``` - * - * @return string */ - public function toRfc7231String() + public function toRfc7231String(): string { return $this->avoidMutation() ->setTimezone('GMT') @@ -451,10 +383,8 @@ trait Converter * ``` * var_dump(Carbon::now()->toArray()); * ``` - * - * @return array */ - public function toArray() + public function toArray(): array { return [ 'year' => $this->year, @@ -479,25 +409,21 @@ trait Converter * ``` * var_dump(Carbon::now()->toObject()); * ``` - * - * @return object */ - public function toObject() + public function toObject(): object { return (object) $this->toArray(); } /** - * Returns english human readable complete date string. + * Returns english human-readable complete date string. * * @example * ``` * echo Carbon::now()->toString(); * ``` - * - * @return string */ - public function toString() + public function toString(): string { return $this->avoidMutation()->locale('en')->isoFormat('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); } @@ -513,20 +439,18 @@ trait Converter * ``` * * @param bool $keepOffset Pass true to keep the date offset. Else forced to UTC. - * - * @return null|string */ - public function toISOString($keepOffset = false) + public function toISOString(bool $keepOffset = false): ?string { if (!$this->isValid()) { return null; } $yearFormat = $this->year < 0 || $this->year > 9999 ? 'YYYYYY' : 'YYYY'; - $tzFormat = $keepOffset ? 'Z' : '[Z]'; + $timezoneFormat = $keepOffset ? 'Z' : '[Z]'; $date = $keepOffset ? $this : $this->avoidMutation()->utc(); - return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$tzFormat"); + return $date->isoFormat("$yearFormat-MM-DD[T]HH:mm:ss.SSSSSS$timezoneFormat"); } /** @@ -536,10 +460,8 @@ trait Converter * ``` * echo Carbon::now('America/Toronto')->toJSON(); * ``` - * - * @return null|string */ - public function toJSON() + public function toJSON(): ?string { return $this->toISOString(); } @@ -551,12 +473,11 @@ trait Converter * ``` * var_dump(Carbon::now()->toDateTime()); * ``` - * - * @return DateTime */ - public function toDateTime() + public function toDateTime(): DateTime { - return new DateTime($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + return DateTime::createFromFormat('U.u', $this->rawFormat('U.u')) + ->setTimezone($this->getTimezone()); } /** @@ -566,12 +487,11 @@ trait Converter * ``` * var_dump(Carbon::now()->toDateTimeImmutable()); * ``` - * - * @return DateTimeImmutable */ - public function toDateTimeImmutable() + public function toDateTimeImmutable(): DateTimeImmutable { - return new DateTimeImmutable($this->rawFormat('Y-m-d H:i:s.u'), $this->getTimezone()); + return DateTimeImmutable::createFromFormat('U.u', $this->rawFormat('U.u')) + ->setTimezone($this->getTimezone()); } /** @@ -583,10 +503,8 @@ trait Converter * ``` * var_dump(Carbon::now()->toDate()); * ``` - * - * @return DateTime */ - public function toDate() + public function toDate(): DateTime { return $this->toDateTime(); } @@ -595,44 +513,44 @@ trait Converter * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). * * @param \DateTimeInterface|Carbon|CarbonImmutable|int|null $end period end date or recurrences count if int - * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param int|\DateInterval|Duration|string|null $interval period default interval or number of the given $unit * @param string|null $unit if specified, $interval must be an integer - * - * @return CarbonPeriod */ - public function toPeriod($end = null, $interval = null, $unit = null) + public function toPeriod($end = null, $interval = null, $unit = null): CarbonPeriod { if ($unit) { $interval = CarbonInterval::make("$interval ".static::pluralUnit($unit)); } - $period = ($this->isMutable() ? new CarbonPeriod() : new CarbonPeriodImmutable()) - ->setDateClass(static::class) - ->setStartDate($this); - - if ($interval) { - $period = $period->setDateInterval($interval); - } + $isDefaultInterval = !$interval; + $interval ??= CarbonInterval::day(); + $class = $this->isMutable() ? CarbonPeriod::class : CarbonPeriodImmutable::class; if (\is_int($end) || (\is_string($end) && ctype_digit($end))) { - $period = $period->setRecurrences($end); - } elseif ($end) { - $period = $period->setEndDate($end); + $end = (int) $end; } - return $period; + $end ??= 1; + + if (!\is_int($end)) { + $end = $this->resolveCarbon($end); + } + + return new $class( + raw: [$this, CarbonInterval::make($interval), $end], + dateClass: static::class, + isDefaultInterval: $isDefaultInterval, + ); } /** * Create a iterable CarbonPeriod object from current date to a given end date (and optional interval). * * @param \DateTimeInterface|Carbon|CarbonImmutable|null $end period end date - * @param int|\DateInterval|string|null $interval period default interval or number of the given $unit + * @param int|\DateInterval|Duration|string|null $interval period default interval or number of the given $unit * @param string|null $unit if specified, $interval must be an integer - * - * @return CarbonPeriod */ - public function range($end = null, $interval = null, $unit = null) + public function range($end = null, $interval = null, $unit = null): CarbonPeriod { return $this->toPeriod($end, $interval, $unit); } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php index 0d611ea22..34e9abf83 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php @@ -1,5 +1,7 @@ constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u'); + public function __construct( + DateTimeInterface|WeekDay|Month|string|int|float|null $time = null, + DateTimeZone|string|int|null $timezone = null, + ) { + $this->initLocalFactory(); + + if ($time instanceof Month) { + $time = $time->name.' 1'; + } elseif ($time instanceof WeekDay) { + $time = $time->name; + } elseif ($time instanceof DateTimeInterface) { + $time = $this->constructTimezoneFromDateTime($time, $timezone)->format('Y-m-d H:i:s.u'); } - if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { + if (\is_string($time) && str_starts_with($time, '@')) { + $time = static::createFromTimestampUTC(substr($time, 1))->format('Y-m-d\TH:i:s.uP'); + } elseif (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP'); } - // If the class has a test now set and we are trying to create a now() + // If the class has a test now set, and we are trying to create a now() // instance then override as required - $isNow = empty($time) || $time === 'now'; + $isNow = \in_array($time, [null, '', 'now'], true); + $timezone = static::safeCreateDateTimeZone($timezone) ?? null; - if (method_exists(static::class, 'hasTestNow') && - method_exists(static::class, 'getTestNow') && - static::hasTestNow() && + if ( + ($this->clock || ( + method_exists(static::class, 'hasTestNow') && + method_exists(static::class, 'getTestNow') && + static::hasTestNow() + )) && ($isNow || static::hasRelativeKeywords($time)) ) { - static::mockConstructorParameters($time, $tz); - } - - // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 - if (!str_contains((string) .1, '.')) { - $locale = setlocale(LC_NUMERIC, '0'); // @codeCoverageIgnore - setlocale(LC_NUMERIC, 'C'); // @codeCoverageIgnore + $this->mockConstructorParameters($time, $timezone); } try { - parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); + parent::__construct($time ?? 'now', $timezone); } catch (Exception $exception) { throw new InvalidFormatException($exception->getMessage(), 0, $exception); } - $this->constructedObjectId = spl_object_hash($this); - - if (isset($locale)) { - setlocale(LC_NUMERIC, $locale); // @codeCoverageIgnore - } + $this->constructedObjectId = spl_object_id($this); self::setLastErrors(parent::getLastErrors()); } /** * Get timezone from a datetime instance. - * - * @param DateTimeInterface $date - * @param DateTimeZone|string|null $tz - * - * @return DateTimeInterface */ - private function constructTimezoneFromDateTime(DateTimeInterface $date, &$tz) - { - if ($tz !== null) { - $safeTz = static::safeCreateDateTimeZone($tz); + private function constructTimezoneFromDateTime( + DateTimeInterface $date, + DateTimeZone|string|int|null &$timezone, + ): DateTimeInterface { + if ($timezone !== null) { + $safeTz = static::safeCreateDateTimeZone($timezone); if ($safeTz) { - return ($date instanceof DateTimeImmutable ? $date : clone $date)->setTimezone($safeTz); + $date = ($date instanceof DateTimeImmutable ? $date : clone $date)->setTimezone($safeTz); } return $date; } - $tz = $date->getTimezone(); + $timezone = $date->getTimezone(); return $date; } @@ -128,27 +132,22 @@ trait Creator /** * Update constructedObjectId on cloned. */ - public function __clone() + public function __clone(): void { - $this->constructedObjectId = spl_object_hash($this); + $this->constructedObjectId = spl_object_id($this); } /** * Create a Carbon instance from a DateTime one. - * - * @param DateTimeInterface $date - * - * @return static */ - public static function instance($date) + public static function instance(DateTimeInterface $date): static { if ($date instanceof static) { return clone $date; } - static::expectDateTime($date); - - $instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone()); + $instance = parent::createFromFormat('U.u', $date->format('U.u')) + ->setTimezone($date->getTimezone()); if ($date instanceof CarbonInterface) { $settings = $date->getSettings(); @@ -170,35 +169,29 @@ trait Creator * as it allows you to do Carbon::parse('Monday next week')->fn() rather * than (new Carbon('Monday next week'))->fn(). * - * @param string|DateTimeInterface|null $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException - * - * @return static */ - public static function rawParse($time = null, $tz = null) - { + public static function rawParse( + DateTimeInterface|WeekDay|Month|string|int|float|null $time, + DateTimeZone|string|int|null $timezone = null, + ): static { if ($time instanceof DateTimeInterface) { return static::instance($time); } try { - return new static($time, $tz); + return new static($time, $timezone); } catch (Exception $exception) { // @codeCoverageIgnoreStart try { - $date = @static::now($tz)->change($time); - } catch (DateMalformedStringException $ignoredException) { + $date = @static::now($timezone)->change($time); + } catch (DateMalformedStringException|InvalidFormatException) { $date = null; } // @codeCoverageIgnoreEnd - if (!$date) { - throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception); - } - - return $date; + return $date + ?? throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception); } } @@ -209,19 +202,16 @@ trait Creator * as it allows you to do Carbon::parse('Monday next week')->fn() rather * than (new Carbon('Monday next week'))->fn(). * - * @param string|DateTimeInterface|null $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException - * - * @return static */ - public static function parse($time = null, $tz = null) - { + public static function parse( + DateTimeInterface|WeekDay|Month|string|int|float|null $time, + DateTimeZone|string|int|null $timezone = null, + ): static { $function = static::$parseFunction; if (!$function) { - return static::rawParse($time, $tz); + return static::rawParse($time, $timezone); } if (\is_string($function) && method_exists(static::class, $function)) { @@ -234,120 +224,77 @@ trait Creator /** * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.). * - * @param string $time date/time string in the given language (may also contain English). - * @param string|null $locale if locale is null or not specified, current global locale will be - * used instead. - * @param DateTimeZone|string|null $tz optional timezone for the new instance. + * @param string $time date/time string in the given language (may also contain English). + * @param string|null $locale if locale is null or not specified, current global locale will be + * used instead. + * @param DateTimeZone|string|int|null $timezone optional timezone for the new instance. * * @throws InvalidFormatException - * - * @return static */ - public static function parseFromLocale($time, $locale = null, $tz = null) - { - return static::rawParse(static::translateTimeString($time, $locale, 'en'), $tz); + public static function parseFromLocale( + string $time, + ?string $locale = null, + DateTimeZone|string|int|null $timezone = null, + ): static { + $text = static::translateTimeString($time, $locale, static::DEFAULT_LOCALE); + + return static::rawParse(str_replace("'", '', $text), $timezone); } /** * Get a Carbon instance for the current date and time. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function now($tz = null) + public static function now(DateTimeZone|string|int|null $timezone = null): static { - return new static(null, $tz); + return new static(null, $timezone); } /** * Create a Carbon instance for today. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function today($tz = null) + public static function today(DateTimeZone|string|int|null $timezone = null): static { - return static::rawParse('today', $tz); + return static::rawParse('today', $timezone); } /** * Create a Carbon instance for tomorrow. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function tomorrow($tz = null) + public static function tomorrow(DateTimeZone|string|int|null $timezone = null): static { - return static::rawParse('tomorrow', $tz); + return static::rawParse('tomorrow', $timezone); } /** * Create a Carbon instance for yesterday. - * - * @param DateTimeZone|string|null $tz - * - * @return static */ - public static function yesterday($tz = null) + public static function yesterday(DateTimeZone|string|int|null $timezone = null): static { - return static::rawParse('yesterday', $tz); + return static::rawParse('yesterday', $timezone); } - /** - * Create a Carbon instance for the greatest supported date. - * - * @return static - */ - public static function maxValue() - { - if (self::$PHPIntSize === 4) { - // 32 bit - return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore - } - - // 64 bit - return static::create(9999, 12, 31, 23, 59, 59); - } - - /** - * Create a Carbon instance for the lowest supported date. - * - * @return static - */ - public static function minValue() - { - if (self::$PHPIntSize === 4) { - // 32 bit - return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore - } - - // 64 bit - return static::create(1, 1, 1, 0, 0, 0); - } - - private static function assertBetween($unit, $value, $min, $max) + private static function assertBetween($unit, $value, $min, $max): void { if (static::isStrictModeEnabled() && ($value < $min || $value > $max)) { throw new OutOfRangeException($unit, $min, $max, $value); } } - private static function createNowInstance($tz) + private static function createNowInstance($timezone) { if (!static::hasTestNow()) { - return static::now($tz); + return static::now($timezone); } $now = static::getTestNow(); if ($now instanceof Closure) { - return $now(static::now($tz)); + return $now(static::now($timezone)); } - return $now->avoidMutation()->tz($tz); + $now = $now->avoidMutation(); + + return $timezone === null ? $now : $now->setTimezone($timezone); } /** @@ -362,28 +309,30 @@ trait Creator * If $hour is not null then the default values for $minute and $second * will be 0. * - * @param DateTimeInterface|int|null $year - * @param int|null $month - * @param int|null $day - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param DateTimeInterface|string|int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null) + public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = null): ?static { + $month = self::monthToInt($month); + if ((\is_string($year) && !is_numeric($year)) || $year instanceof DateTimeInterface) { - return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null)); + return static::parse($year, $timezone ?? (\is_string($month) || $month instanceof DateTimeZone ? $month : null)); } $defaults = null; - $getDefault = function ($unit) use ($tz, &$defaults) { + $getDefault = function ($unit) use ($timezone, &$defaults) { if ($defaults === null) { - $now = self::createNowInstance($tz); + $now = self::createNowInstance($timezone); $defaults = array_combine([ 'year', @@ -422,13 +371,13 @@ trait Creator } $second = ($second < 10 ? '0' : '').number_format($second, 6); - $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', \sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $timezone); - if ($fixYear !== null) { + if ($instance && $fixYear !== null) { $instance = $instance->addYears($fixYear); } - return $instance; + return $instance ?? null; } /** @@ -446,20 +395,21 @@ trait Creator * If one of the set values is not valid, an InvalidDateException * will be thrown. * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidDateException * - * @return static|false + * @return static|null */ - public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $timezone = null): ?static { + $month = self::monthToInt($month); $fields = static::getRangesByUnit(); foreach ($fields as $field => $range) { @@ -468,11 +418,11 @@ trait Creator throw new InvalidDateException($field, $$field); } - return false; + return null; } } - $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz); + $instance = static::create($year, $month, $day, $hour, $minute, $second, $timezone); foreach (array_reverse($fields) as $field => $range) { if ($$field !== null && (!\is_int($$field) || $$field !== $instance->$field)) { @@ -480,7 +430,7 @@ trait Creator throw new InvalidDateException($field, $$field); } - return false; + return null; } } @@ -492,25 +442,25 @@ trait Creator * * @see create() * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null): self + public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $timezone = null): static { $initialStrictMode = static::isStrictModeEnabled(); static::useStrictMode(true); try { - $date = static::create($year, $month, $day, $hour, $minute, $second, $tz); + $date = static::create($year, $month, $day, $hour, $minute, $second, $timezone); } finally { static::useStrictMode($initialStrictMode); } @@ -521,114 +471,101 @@ trait Creator /** * Create a Carbon instance from just a date. The time portion is set to now. * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + public static function createFromDate($year = null, $month = null, $day = null, $timezone = null) { - return static::create($year, $month, $day, null, null, null, $tz); + return static::create($year, $month, $day, null, null, null, $timezone); } /** * Create a Carbon instance from just a date. The time portion is set to midnight. * - * @param int|null $year - * @param int|null $month - * @param int|null $day - * @param DateTimeZone|string|null $tz + * @param int|null $year + * @param int|null $month + * @param int|null $day + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null) + public static function createMidnightDate($year = null, $month = null, $day = null, $timezone = null) { - return static::create($year, $month, $day, 0, 0, 0, $tz); + return static::create($year, $month, $day, 0, 0, 0, $timezone); } /** * Create a Carbon instance from just a time. The date portion is set to today. * - * @param int|null $hour - * @param int|null $minute - * @param int|null $second - * @param DateTimeZone|string|null $tz + * @param int|null $hour + * @param int|null $minute + * @param int|null $second + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * * @return static */ - public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null) + public static function createFromTime($hour = 0, $minute = 0, $second = 0, $timezone = null): static { - return static::create(null, null, null, $hour, $minute, $second, $tz); + return static::create(null, null, null, $hour, $minute, $second, $timezone); } /** * Create a Carbon instance from a time string. The date portion is set to today. * - * @param string $time - * @param DateTimeZone|string|null $tz - * * @throws InvalidFormatException - * - * @return static */ - public static function createFromTimeString($time, $tz = null) + public static function createFromTimeString(string $time, DateTimeZone|string|int|null $timezone = null): static { - return static::today($tz)->setTimeFromTimeString($time); + return static::today($timezone)->setTimeFromTimeString($time); } - /** - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $originalTz - * - * @return DateTimeInterface|false - */ - private static function createFromFormatAndTimezone($format, $time, $originalTz) + private static function createFromFormatAndTimezone( + string $format, + string $time, + DateTimeZone|string|int|null $originalTimezone, + ): ?DateTimeInterface { + if ($originalTimezone === null) { + return parent::createFromFormat($format, $time) ?: null; + } + + $timezone = \is_int($originalTimezone) ? self::getOffsetTimezone($originalTimezone) : $originalTimezone; + + $timezone = static::safeCreateDateTimeZone($timezone, $originalTimezone); + + return parent::createFromFormat($format, $time, $timezone) ?: null; + } + + private static function getOffsetTimezone(int $offset): string { - // Work-around for https://bugs.php.net/bug.php?id=75577 - // @codeCoverageIgnoreStart - if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) { - $format = str_replace('.v', '.u', $format); - } - // @codeCoverageIgnoreEnd + $minutes = (int) ($offset * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE); - if ($originalTz === null) { - return parent::createFromFormat($format, (string) $time); - } - - $tz = \is_int($originalTz) - ? @timezone_name_from_abbr('', (int) ($originalTz * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE), 1) - : $originalTz; - - $tz = static::safeCreateDateTimeZone($tz, $originalTz); - - if ($tz === false) { - return false; - } - - return parent::createFromFormat($format, (string) $time, $tz); + return @timezone_name_from_abbr('', $minutes, 1) ?: throw new InvalidTimeZoneException( + "Invalid offset timezone $offset", + ); } /** * Create a Carbon instance from a specific format. * - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function rawCreateFromFormat($format, $time, $tz = null) + public static function rawCreateFromFormat(string $format, string $time, $timezone = null): ?static { // Work-around for https://bugs.php.net/bug.php?id=80141 $format = preg_replace('/(?getTimezone(); + if ($timezone === null && !preg_match("/{$nonEscaped}[eOPT]/", $nonIgnored)) { + $timezone = clone $mock->getTimezone(); } $mock = $mock->copy(); @@ -676,7 +613,7 @@ trait Creator } // Regenerate date from the modified format to base result on the mocked instance instead of now. - $date = self::createFromFormatAndTimezone($format, $time, $tz); + $date = self::createFromFormatAndTimezone($format, $time, $timezone); } if ($date instanceof DateTimeInterface) { @@ -687,30 +624,43 @@ trait Creator } if (static::isStrictModeEnabled()) { - throw new InvalidFormatException(implode(PHP_EOL, $lastErrors['errors'])); + throw new InvalidFormatException(implode(PHP_EOL, (array) $lastErrors['errors'])); } - return false; + return null; } /** * Create a Carbon instance from a specific format. * - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ #[ReturnTypeWillChange] - public static function createFromFormat($format, $time, $tz = null) + public static function createFromFormat($format, $time, $timezone = null): ?static { $function = static::$createFromFormatFunction; + // format is a single numeric unit + if (\is_int($time) && \in_array(ltrim($format, '!'), ['U', 'Y', 'y', 'X', 'x', 'm', 'n', 'd', 'j', 'w', 'W', 'H', 'h', 'G', 'g', 'i', 's', 'u', 'z', 'v'], true)) { + $time = (string) $time; + } + + if (!\is_string($time)) { + @trigger_error( + 'createFromFormat() $time parameter will only accept string or integer for 1-letter format representing a numeric unit in the next version', + \E_USER_DEPRECATED, + ); + $time = (string) $time; + } + if (!$function) { - return static::rawCreateFromFormat($format, $time, $tz); + return static::rawCreateFromFormat($format, $time, $timezone); } if (\is_string($function) && method_exists(static::class, $function)) { @@ -723,48 +673,63 @@ trait Creator /** * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()). * - * @param string $format Datetime format - * @param string $time - * @param DateTimeZone|string|false|null $tz optional timezone - * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) - * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats + * Era tokens (N to NNNNN, y, yy, yyy, yyyy, yo) are read with the "eras" of $locale: the era name gives the + * era and the year is the year of this era, so 'D MMMM N y' with 'th' reads '29 กุมภาพันธ์ พ.ศ. 2567' as + * 2024-02-29. Without any N token, the era of today's date in this locale is used. + * + * @param string $format Datetime format + * @param string $time + * @param DateTimeZone|string|int|null $timezone optional timezone + * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use) + * @param TranslatorInterface|null $translator optional custom translator to use for macro-formats * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null) - { + public static function createFromIsoFormat( + string $format, + string $time, + $timezone = null, + ?string $locale = CarbonInterface::DEFAULT_LOCALE, + ?TranslatorInterface $translator = null + ): ?static { $format = preg_replace_callback('/(? static::getTranslationMessageWith($translator, 'formats.LT', $locale, 'h:mm A'), - 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale, 'h:mm:ss A'), - 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale, 'MM/DD/YYYY'), - 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale, 'MMMM D, YYYY'), - 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), - 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + 'LT' => static::getTranslationMessageWith($translator, 'formats.LT', $locale), + 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale), + 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale), + 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale), + 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale), + 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale), ]; } return $formats[$code] ?? preg_replace_callback( '/MMMM|MM|DD|dddd/', - function ($code) { - return mb_substr($code[0], 1); - }, - $formats[strtoupper($code)] ?? '' + static fn (array $code) => mb_substr($code[0], 1), + $formats[strtoupper($code)] ?? '', ); }, $format); - $format = preg_replace_callback('/(?subDay(); + } + + return $date; } /** * Create a Carbon instance from a specific format and a string in a given language. * - * @param string $format Datetime format - * @param string $locale - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function createFromLocaleFormat($format, $locale, $time, $tz = null) + public static function createFromLocaleFormat(string $format, string $locale, string $time, $timezone = null): ?static { $format = preg_replace_callback( '/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/', static function (array $match) use ($locale): string { $word = str_replace('\\', '', $match[0]); - $translatedWord = static::translateTimeString($word, $locale, 'en'); + $translatedWord = static::translateTimeString($word, $locale, static::DEFAULT_LOCALE); return $word === $translatedWord ? $match[0] @@ -889,26 +860,39 @@ trait Creator $format ); - return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz); + return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, static::DEFAULT_LOCALE), $timezone); } /** * Create a Carbon instance from a specific ISO format and a string in a given language. * - * @param string $format Datetime ISO format - * @param string $locale - * @param string $time - * @param DateTimeZone|string|false|null $tz + * @param string $format Datetime ISO format + * @param string $locale + * @param string $time + * @param DateTimeZone|string|int|null $timezone * * @throws InvalidFormatException * - * @return static|false + * @return static|null */ - public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null) + public static function createFromLocaleIsoFormat(string $format, string $locale, string $time, $timezone = null): ?static { - $time = static::translateTimeString($time, $locale, 'en', CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); + $translatedTime = static::translateTimeString($time, $locale, static::DEFAULT_LOCALE, CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM); - return static::createFromIsoFormat($format, $time, $tz, $locale); + try { + return static::createFromIsoFormat($format, $translatedTime, $timezone, $locale); + } catch (InvalidFormatException $exception) { + // Show the given string in error messages rather than its translation to English + if ($translatedTime === $time || !str_contains($exception->getMessage(), "'$translatedTime'")) { + throw $exception; + } + + throw new InvalidFormatException( + str_replace("'$translatedTime'", "'$time'", $exception->getMessage()), + $exception->getCode(), + $exception, + ); + } } /** @@ -923,7 +907,7 @@ trait Creator * * @return static|null */ - public static function make($var) + public static function make($var, DateTimeZone|string|null $timezone = null): ?static { if ($var instanceof DateTimeInterface) { return static::instance($var); @@ -938,7 +922,7 @@ trait Creator !preg_match('/^R\d/', $var) && preg_match('/[a-z\d]/i', $var) ) { - $date = static::parse($var); + $date = static::parse($var, $timezone); } } @@ -952,26 +936,29 @@ trait Creator * * @return void */ - private static function setLastErrors($lastErrors) + private static function setLastErrors($lastErrors): void { - if (\is_array($lastErrors) || $lastErrors === false) { - static::$lastErrors = \is_array($lastErrors) ? $lastErrors : [ - 'warning_count' => 0, - 'warnings' => [], - 'error_count' => 0, - 'errors' => [], - ]; - } + static::$lastErrors = $lastErrors; } /** * {@inheritdoc} - * - * @return array */ - #[ReturnTypeWillChange] - public static function getLastErrors() + public static function getLastErrors(): array|false { return static::$lastErrors; } + + private static function monthToInt(mixed $value, string $unit = 'month'): mixed + { + if ($value instanceof Month) { + if ($unit !== 'month') { + throw new UnitException("Month enum cannot be used to set $unit"); + } + + return Month::int($value); + } + + return $value; + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php index 8ae5c1781..5f3d21b39 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Date.php @@ -1,5 +1,7 @@ * + * @property string $localeDayOfWeek the day of week in current locale + * @property string $shortLocaleDayOfWeek the abbreviated day of week in current locale + * @property string $localeMonth the month in current locale + * @property string $shortLocaleMonth the abbreviated month in current locale * @property int $year * @property int $yearIso * @property int $month @@ -52,475 +65,803 @@ use Throwable; * @property int $second * @property int $micro * @property int $microsecond - * @property int|float|string $timestamp seconds since the Unix Epoch - * @property string $englishDayOfWeek the day of week in English - * @property string $shortEnglishDayOfWeek the abbreviated day of week in English - * @property string $englishMonth the month in English - * @property string $shortEnglishMonth the abbreviated month in English + * @property int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) + * @property int|float|string $timestamp seconds since the Unix Epoch + * @property string $englishDayOfWeek the day of week in English + * @property string $shortEnglishDayOfWeek the abbreviated day of week in English + * @property string $englishMonth the month in English + * @property string $shortEnglishMonth the abbreviated month in English * @property int $milliseconds * @property int $millisecond * @property int $milli - * @property int $week 1 through 53 - * @property int $isoWeek 1 through 53 - * @property int $weekYear year according to week format - * @property int $isoWeekYear year according to ISO week format - * @property int $dayOfYear 1 through 366 - * @property int $age does a diffInYears() with default parameters - * @property int $offset the timezone offset in seconds from UTC - * @property int $offsetMinutes the timezone offset in minutes from UTC - * @property int $offsetHours the timezone offset in hours from UTC - * @property CarbonTimeZone $timezone the current timezone - * @property CarbonTimeZone $tz alias of $timezone - * @property-read int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) - * @property-read int $dayOfWeekIso 1 (for Monday) through 7 (for Sunday) - * @property-read int $weekOfYear ISO-8601 week number of year, weeks starting on Monday - * @property-read int $daysInMonth number of days in the given month - * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) - * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) - * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name - * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName - * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language - * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language - * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language - * @property-read int $noZeroHour current hour from 1 to 24 - * @property-read int $weeksInYear 51 through 53 - * @property-read int $isoWeeksInYear 51 through 53 - * @property-read int $weekOfMonth 1 through 5 - * @property-read int $weekNumberInMonth 1 through 5 - * @property-read int $firstWeekDay 0 through 6 - * @property-read int $lastWeekDay 0 through 6 - * @property-read int $daysInYear 365 or 366 - * @property-read int $quarter the quarter of this instance, 1 - 4 - * @property-read int $decade the decade of this instance - * @property-read int $century the century of this instance - * @property-read int $millennium the millennium of this instance - * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise - * @property-read bool $local checks if the timezone is local, true if local, false otherwise - * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise - * @property-read string $timezoneName the current timezone name - * @property-read string $tzName alias of $timezoneName - * @property-read string $locale locale of the current instance + * @property int $week 1 through 53 + * @property int $isoWeek 1 through 53 + * @property int $weekYear year according to week format + * @property int $isoWeekYear year according to ISO week format + * @property int $age does a diffInYears() with default parameters + * @property int $offset the timezone offset in seconds from UTC + * @property int $offsetMinutes the timezone offset in minutes from UTC + * @property int $offsetHours the timezone offset in hours from UTC + * @property CarbonTimeZone $timezone the current timezone + * @property CarbonTimeZone $tz alias of $timezone + * @property int $centuryOfMillennium The value of the century starting from the beginning of the current millennium + * @property int $dayOfCentury The value of the day starting from the beginning of the current century + * @property int $dayOfDecade The value of the day starting from the beginning of the current decade + * @property int $dayOfMillennium The value of the day starting from the beginning of the current millennium + * @property int $dayOfMonth The value of the day starting from the beginning of the current month + * @property int $dayOfQuarter The value of the day starting from the beginning of the current quarter + * @property int $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property int $dayOfYear 1 through 366 + * @property int $decadeOfCentury The value of the decade starting from the beginning of the current century + * @property int $decadeOfMillennium The value of the decade starting from the beginning of the current millennium + * @property int $hourOfCentury The value of the hour starting from the beginning of the current century + * @property int $hourOfDay The value of the hour starting from the beginning of the current day + * @property int $hourOfDecade The value of the hour starting from the beginning of the current decade + * @property int $hourOfMillennium The value of the hour starting from the beginning of the current millennium + * @property int $hourOfMonth The value of the hour starting from the beginning of the current month + * @property int $hourOfQuarter The value of the hour starting from the beginning of the current quarter + * @property int $hourOfWeek The value of the hour starting from the beginning of the current week + * @property int $hourOfYear The value of the hour starting from the beginning of the current year + * @property int $microsecondOfCentury The value of the microsecond starting from the beginning of the current century + * @property int $microsecondOfDay The value of the microsecond starting from the beginning of the current day + * @property int $microsecondOfDecade The value of the microsecond starting from the beginning of the current decade + * @property int $microsecondOfHour The value of the microsecond starting from the beginning of the current hour + * @property int $microsecondOfMillennium The value of the microsecond starting from the beginning of the current millennium + * @property int $microsecondOfMillisecond The value of the microsecond starting from the beginning of the current millisecond + * @property int $microsecondOfMinute The value of the microsecond starting from the beginning of the current minute + * @property int $microsecondOfMonth The value of the microsecond starting from the beginning of the current month + * @property int $microsecondOfQuarter The value of the microsecond starting from the beginning of the current quarter + * @property int $microsecondOfSecond The value of the microsecond starting from the beginning of the current second + * @property int $microsecondOfWeek The value of the microsecond starting from the beginning of the current week + * @property int $microsecondOfYear The value of the microsecond starting from the beginning of the current year + * @property int $millisecondOfCentury The value of the millisecond starting from the beginning of the current century + * @property int $millisecondOfDay The value of the millisecond starting from the beginning of the current day + * @property int $millisecondOfDecade The value of the millisecond starting from the beginning of the current decade + * @property int $millisecondOfHour The value of the millisecond starting from the beginning of the current hour + * @property int $millisecondOfMillennium The value of the millisecond starting from the beginning of the current millennium + * @property int $millisecondOfMinute The value of the millisecond starting from the beginning of the current minute + * @property int $millisecondOfMonth The value of the millisecond starting from the beginning of the current month + * @property int $millisecondOfQuarter The value of the millisecond starting from the beginning of the current quarter + * @property int $millisecondOfSecond The value of the millisecond starting from the beginning of the current second + * @property int $millisecondOfWeek The value of the millisecond starting from the beginning of the current week + * @property int $millisecondOfYear The value of the millisecond starting from the beginning of the current year + * @property int $minuteOfCentury The value of the minute starting from the beginning of the current century + * @property int $minuteOfDay The value of the minute starting from the beginning of the current day + * @property int $minuteOfDecade The value of the minute starting from the beginning of the current decade + * @property int $minuteOfHour The value of the minute starting from the beginning of the current hour + * @property int $minuteOfMillennium The value of the minute starting from the beginning of the current millennium + * @property int $minuteOfMonth The value of the minute starting from the beginning of the current month + * @property int $minuteOfQuarter The value of the minute starting from the beginning of the current quarter + * @property int $minuteOfWeek The value of the minute starting from the beginning of the current week + * @property int $minuteOfYear The value of the minute starting from the beginning of the current year + * @property int $monthOfCentury The value of the month starting from the beginning of the current century + * @property int $monthOfDecade The value of the month starting from the beginning of the current decade + * @property int $monthOfMillennium The value of the month starting from the beginning of the current millennium + * @property int $monthOfQuarter The value of the month starting from the beginning of the current quarter + * @property int $monthOfYear The value of the month starting from the beginning of the current year + * @property int $quarterOfCentury The value of the quarter starting from the beginning of the current century + * @property int $quarterOfDecade The value of the quarter starting from the beginning of the current decade + * @property int $quarterOfMillennium The value of the quarter starting from the beginning of the current millennium + * @property int $quarterOfYear The value of the quarter starting from the beginning of the current year + * @property int $secondOfCentury The value of the second starting from the beginning of the current century + * @property int $secondOfDay The value of the second starting from the beginning of the current day + * @property int $secondOfDecade The value of the second starting from the beginning of the current decade + * @property int $secondOfHour The value of the second starting from the beginning of the current hour + * @property int $secondOfMillennium The value of the second starting from the beginning of the current millennium + * @property int $secondOfMinute The value of the second starting from the beginning of the current minute + * @property int $secondOfMonth The value of the second starting from the beginning of the current month + * @property int $secondOfQuarter The value of the second starting from the beginning of the current quarter + * @property int $secondOfWeek The value of the second starting from the beginning of the current week + * @property int $secondOfYear The value of the second starting from the beginning of the current year + * @property int $weekOfCentury The value of the week starting from the beginning of the current century + * @property int $weekOfDecade The value of the week starting from the beginning of the current decade + * @property int $weekOfMillennium The value of the week starting from the beginning of the current millennium + * @property int $weekOfMonth 1 through 5 + * @property int $weekOfQuarter The value of the week starting from the beginning of the current quarter + * @property int $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property int $yearOfCentury The value of the year starting from the beginning of the current century + * @property int $yearOfDecade The value of the year starting from the beginning of the current decade + * @property int $yearOfMillennium The value of the year starting from the beginning of the current millennium + * @property-read string $latinMeridiem "am"/"pm" (Ante meridiem or Post meridiem latin lowercase mark) + * @property-read string $latinUpperMeridiem "AM"/"PM" (Ante meridiem or Post meridiem latin uppercase mark) + * @property-read string $timezoneAbbreviatedName the current timezone abbreviated name + * @property-read string $tzAbbrName alias of $timezoneAbbreviatedName + * @property-read string $dayName long name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortDayName short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $minDayName very short name of weekday translated according to Carbon locale, in english if no translation available for current language + * @property-read string $monthName long name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $shortMonthName short name of month translated according to Carbon locale, in english if no translation available for current language + * @property-read string $meridiem lowercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read string $upperMeridiem uppercase meridiem mark translated according to Carbon locale, in latin if no translation available for current language + * @property-read int $noZeroHour current hour from 1 to 24 + * @property-read int $isoWeeksInYear 51 through 53 + * @property-read int $weekNumberInMonth 1 through 5 + * @property-read int $firstWeekDay 0 through 6 + * @property-read int $lastWeekDay 0 through 6 + * @property-read int $quarter the quarter of this instance, 1 - 4 + * @property-read int $decade the decade of this instance + * @property-read int $century the century of this instance + * @property-read int $millennium the millennium of this instance + * @property-read bool $dst daylight savings time indicator, true if DST, false otherwise + * @property-read bool $local checks if the timezone is local, true if local, false otherwise + * @property-read bool $utc checks if the timezone is UTC, true if UTC, false otherwise + * @property-read string $timezoneName the current timezone name + * @property-read string $tzName alias of $timezoneName + * @property-read string $locale locale of the current instance + * @property-read int $centuriesInMillennium The number of centuries contained in the current millennium + * @property-read int $daysInCentury The number of days contained in the current century + * @property-read int $daysInDecade The number of days contained in the current decade + * @property-read int $daysInMillennium The number of days contained in the current millennium + * @property-read int $daysInMonth number of days in the given month + * @property-read int $daysInQuarter The number of days contained in the current quarter + * @property-read int $daysInWeek The number of days contained in the current week + * @property-read int $daysInYear 365 or 366 + * @property-read int $decadesInCentury The number of decades contained in the current century + * @property-read int $decadesInMillennium The number of decades contained in the current millennium + * @property-read int $hoursInCentury The number of hours contained in the current century + * @property-read int $hoursInDay The number of hours contained in the current day + * @property-read int $hoursInDecade The number of hours contained in the current decade + * @property-read int $hoursInMillennium The number of hours contained in the current millennium + * @property-read int $hoursInMonth The number of hours contained in the current month + * @property-read int $hoursInQuarter The number of hours contained in the current quarter + * @property-read int $hoursInWeek The number of hours contained in the current week + * @property-read int $hoursInYear The number of hours contained in the current year + * @property-read int $microsecondsInCentury The number of microseconds contained in the current century + * @property-read int $microsecondsInDay The number of microseconds contained in the current day + * @property-read int $microsecondsInDecade The number of microseconds contained in the current decade + * @property-read int $microsecondsInHour The number of microseconds contained in the current hour + * @property-read int $microsecondsInMillennium The number of microseconds contained in the current millennium + * @property-read int $microsecondsInMillisecond The number of microseconds contained in the current millisecond + * @property-read int $microsecondsInMinute The number of microseconds contained in the current minute + * @property-read int $microsecondsInMonth The number of microseconds contained in the current month + * @property-read int $microsecondsInQuarter The number of microseconds contained in the current quarter + * @property-read int $microsecondsInSecond The number of microseconds contained in the current second + * @property-read int $microsecondsInWeek The number of microseconds contained in the current week + * @property-read int $microsecondsInYear The number of microseconds contained in the current year + * @property-read int $millisecondsInCentury The number of milliseconds contained in the current century + * @property-read int $millisecondsInDay The number of milliseconds contained in the current day + * @property-read int $millisecondsInDecade The number of milliseconds contained in the current decade + * @property-read int $millisecondsInHour The number of milliseconds contained in the current hour + * @property-read int $millisecondsInMillennium The number of milliseconds contained in the current millennium + * @property-read int $millisecondsInMinute The number of milliseconds contained in the current minute + * @property-read int $millisecondsInMonth The number of milliseconds contained in the current month + * @property-read int $millisecondsInQuarter The number of milliseconds contained in the current quarter + * @property-read int $millisecondsInSecond The number of milliseconds contained in the current second + * @property-read int $millisecondsInWeek The number of milliseconds contained in the current week + * @property-read int $millisecondsInYear The number of milliseconds contained in the current year + * @property-read int $minutesInCentury The number of minutes contained in the current century + * @property-read int $minutesInDay The number of minutes contained in the current day + * @property-read int $minutesInDecade The number of minutes contained in the current decade + * @property-read int $minutesInHour The number of minutes contained in the current hour + * @property-read int $minutesInMillennium The number of minutes contained in the current millennium + * @property-read int $minutesInMonth The number of minutes contained in the current month + * @property-read int $minutesInQuarter The number of minutes contained in the current quarter + * @property-read int $minutesInWeek The number of minutes contained in the current week + * @property-read int $minutesInYear The number of minutes contained in the current year + * @property-read int $monthsInCentury The number of months contained in the current century + * @property-read int $monthsInDecade The number of months contained in the current decade + * @property-read int $monthsInMillennium The number of months contained in the current millennium + * @property-read int $monthsInQuarter The number of months contained in the current quarter + * @property-read int $monthsInYear The number of months contained in the current year + * @property-read int $quartersInCentury The number of quarters contained in the current century + * @property-read int $quartersInDecade The number of quarters contained in the current decade + * @property-read int $quartersInMillennium The number of quarters contained in the current millennium + * @property-read int $quartersInYear The number of quarters contained in the current year + * @property-read int $secondsInCentury The number of seconds contained in the current century + * @property-read int $secondsInDay The number of seconds contained in the current day + * @property-read int $secondsInDecade The number of seconds contained in the current decade + * @property-read int $secondsInHour The number of seconds contained in the current hour + * @property-read int $secondsInMillennium The number of seconds contained in the current millennium + * @property-read int $secondsInMinute The number of seconds contained in the current minute + * @property-read int $secondsInMonth The number of seconds contained in the current month + * @property-read int $secondsInQuarter The number of seconds contained in the current quarter + * @property-read int $secondsInWeek The number of seconds contained in the current week + * @property-read int $secondsInYear The number of seconds contained in the current year + * @property-read int $weeksInCentury The number of weeks contained in the current century + * @property-read int $weeksInDecade The number of weeks contained in the current decade + * @property-read int $weeksInMillennium The number of weeks contained in the current millennium + * @property-read int $weeksInMonth The number of weeks contained in the current month + * @property-read int $weeksInQuarter The number of weeks contained in the current quarter + * @property-read int $weeksInYear 51 through 53 + * @property-read int $yearsInCentury The number of years contained in the current century + * @property-read int $yearsInDecade The number of years contained in the current decade + * @property-read int $yearsInMillennium The number of years contained in the current millennium * - * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) - * @method bool isLocal() Check if the current instance has non-UTC timezone. - * @method bool isValid() Check if the current instance is a valid date. - * @method bool isDST() Check if the current instance is in a daylight saving time. - * @method bool isSunday() Checks if the instance day is sunday. - * @method bool isMonday() Checks if the instance day is monday. - * @method bool isTuesday() Checks if the instance day is tuesday. - * @method bool isWednesday() Checks if the instance day is wednesday. - * @method bool isThursday() Checks if the instance day is thursday. - * @method bool isFriday() Checks if the instance day is friday. - * @method bool isSaturday() Checks if the instance day is saturday. - * @method bool isSameYear(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. - * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. - * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. - * @method bool isSameWeek(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. - * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. - * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. - * @method bool isSameDay(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. - * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. - * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. - * @method bool isSameHour(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. - * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. - * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. - * @method bool isSameMinute(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. - * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. - * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. - * @method bool isSameSecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. - * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. - * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. - * @method bool isSameMicro(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isSameMicrosecond(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. - * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. - * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. - * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. - * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. - * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. - * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. - * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. - * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. - * @method bool isSameDecade(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. - * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. - * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. - * @method bool isSameCentury(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. - * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. - * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. - * @method bool isSameMillennium(Carbon|DateTimeInterface|string|null $date = null) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). - * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. - * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. - * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. - * @method CarbonInterface years(int $value) Set current instance year to the given value. - * @method CarbonInterface year(int $value) Set current instance year to the given value. - * @method CarbonInterface setYears(int $value) Set current instance year to the given value. - * @method CarbonInterface setYear(int $value) Set current instance year to the given value. - * @method CarbonInterface months(int $value) Set current instance month to the given value. - * @method CarbonInterface month(int $value) Set current instance month to the given value. - * @method CarbonInterface setMonths(int $value) Set current instance month to the given value. - * @method CarbonInterface setMonth(int $value) Set current instance month to the given value. - * @method CarbonInterface days(int $value) Set current instance day to the given value. - * @method CarbonInterface day(int $value) Set current instance day to the given value. - * @method CarbonInterface setDays(int $value) Set current instance day to the given value. - * @method CarbonInterface setDay(int $value) Set current instance day to the given value. - * @method CarbonInterface hours(int $value) Set current instance hour to the given value. - * @method CarbonInterface hour(int $value) Set current instance hour to the given value. - * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. - * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. - * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. - * @method CarbonInterface minute(int $value) Set current instance minute to the given value. - * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. - * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. - * @method CarbonInterface seconds(int $value) Set current instance second to the given value. - * @method CarbonInterface second(int $value) Set current instance second to the given value. - * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. - * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. - * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. - * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface setMicrosecond(int $value) Set current instance microsecond to the given value. - * @method CarbonInterface addYears(int $value = 1) Add years (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addYear() Add one year to the instance (using date interval). - * @method CarbonInterface subYears(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subYear() Sub one year to the instance (using date interval). - * @method CarbonInterface addYearsWithOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subYearsWithOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addYearsWithoutOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearsWithoutOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearsWithNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearsWithNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearsNoOverflow(int $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearsNoOverflow(int $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonths(int $value = 1) Add months (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMonth() Add one month to the instance (using date interval). - * @method CarbonInterface subMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). - * @method CarbonInterface addMonthsWithOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMonthsWithOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMonthsWithoutOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthsWithoutOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthsWithNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthsWithNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthsNoOverflow(int $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthsNoOverflow(int $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDays(int $value = 1) Add days (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addDay() Add one day to the instance (using date interval). - * @method CarbonInterface subDays(int $value = 1) Sub days (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subDay() Sub one day to the instance (using date interval). - * @method CarbonInterface addHours(int $value = 1) Add hours (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addHour() Add one hour to the instance (using date interval). - * @method CarbonInterface subHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). - * @method CarbonInterface addMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). - * @method CarbonInterface subMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). - * @method CarbonInterface addSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addSecond() Add one second to the instance (using date interval). - * @method CarbonInterface subSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). - * @method CarbonInterface addMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). - * @method CarbonInterface subMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). - * @method CarbonInterface addMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). - * @method CarbonInterface subMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). - * @method CarbonInterface addMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). - * @method CarbonInterface subMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). - * @method CarbonInterface addMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). - * @method CarbonInterface subMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). - * @method CarbonInterface addMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). - * @method CarbonInterface subMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). - * @method CarbonInterface addMillenniaWithOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMillenniaWithOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addMillenniaWithoutOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniaWithoutOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniaWithNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniaWithNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniaNoOverflow(int $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniaNoOverflow(int $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addCentury() Add one century to the instance (using date interval). - * @method CarbonInterface subCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). - * @method CarbonInterface addCenturiesWithOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subCenturiesWithOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addCenturiesWithoutOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturiesWithoutOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturiesWithNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturiesWithNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturiesNoOverflow(int $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturiesNoOverflow(int $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). - * @method CarbonInterface subDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). - * @method CarbonInterface addDecadesWithOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subDecadesWithOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addDecadesWithoutOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadesWithoutOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadesWithNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadesWithNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadesNoOverflow(int $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadesNoOverflow(int $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). - * @method CarbonInterface subQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). - * @method CarbonInterface addQuartersWithOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subQuartersWithOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. - * @method CarbonInterface addQuartersWithoutOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuartersWithoutOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuartersWithNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuartersWithNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuartersNoOverflow(int $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuartersNoOverflow(int $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. - * @method CarbonInterface addWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addWeek() Add one week to the instance (using date interval). - * @method CarbonInterface subWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). - * @method CarbonInterface addWeekdays(int $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). - * @method CarbonInterface subWeekdays(int $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). - * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). - * @method CarbonInterface addRealMicros(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMicro() Add one microsecond to the instance (using timestamp). - * @method CarbonInterface subRealMicros(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMicro() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method CarbonInterface addRealMicroseconds(int $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMicrosecond() Add one microsecond to the instance (using timestamp). - * @method CarbonInterface subRealMicroseconds(int $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMicrosecond() Sub one microsecond to the instance (using timestamp). - * @method CarbonPeriod microsecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. - * @method CarbonInterface addRealMillis(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMilli() Add one millisecond to the instance (using timestamp). - * @method CarbonInterface subRealMillis(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMilli() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method CarbonInterface addRealMilliseconds(int $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMillisecond() Add one millisecond to the instance (using timestamp). - * @method CarbonInterface subRealMilliseconds(int $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMillisecond() Sub one millisecond to the instance (using timestamp). - * @method CarbonPeriod millisecondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. - * @method CarbonInterface addRealSeconds(int $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealSecond() Add one second to the instance (using timestamp). - * @method CarbonInterface subRealSeconds(int $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealSecond() Sub one second to the instance (using timestamp). - * @method CarbonPeriod secondsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. - * @method CarbonInterface addRealMinutes(int $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMinute() Add one minute to the instance (using timestamp). - * @method CarbonInterface subRealMinutes(int $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMinute() Sub one minute to the instance (using timestamp). - * @method CarbonPeriod minutesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. - * @method CarbonInterface addRealHours(int $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealHour() Add one hour to the instance (using timestamp). - * @method CarbonInterface subRealHours(int $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealHour() Sub one hour to the instance (using timestamp). - * @method CarbonPeriod hoursUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. - * @method CarbonInterface addRealDays(int $value = 1) Add days (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealDay() Add one day to the instance (using timestamp). - * @method CarbonInterface subRealDays(int $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealDay() Sub one day to the instance (using timestamp). - * @method CarbonPeriod daysUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. - * @method CarbonInterface addRealWeeks(int $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealWeek() Add one week to the instance (using timestamp). - * @method CarbonInterface subRealWeeks(int $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealWeek() Sub one week to the instance (using timestamp). - * @method CarbonPeriod weeksUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. - * @method CarbonInterface addRealMonths(int $value = 1) Add months (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMonth() Add one month to the instance (using timestamp). - * @method CarbonInterface subRealMonths(int $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMonth() Sub one month to the instance (using timestamp). - * @method CarbonPeriod monthsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. - * @method CarbonInterface addRealQuarters(int $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealQuarter() Add one quarter to the instance (using timestamp). - * @method CarbonInterface subRealQuarters(int $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealQuarter() Sub one quarter to the instance (using timestamp). - * @method CarbonPeriod quartersUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. - * @method CarbonInterface addRealYears(int $value = 1) Add years (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealYear() Add one year to the instance (using timestamp). - * @method CarbonInterface subRealYears(int $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealYear() Sub one year to the instance (using timestamp). - * @method CarbonPeriod yearsUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. - * @method CarbonInterface addRealDecades(int $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealDecade() Add one decade to the instance (using timestamp). - * @method CarbonInterface subRealDecades(int $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealDecade() Sub one decade to the instance (using timestamp). - * @method CarbonPeriod decadesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. - * @method CarbonInterface addRealCenturies(int $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealCentury() Add one century to the instance (using timestamp). - * @method CarbonInterface subRealCenturies(int $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealCentury() Sub one century to the instance (using timestamp). - * @method CarbonPeriod centuriesUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. - * @method CarbonInterface addRealMillennia(int $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface addRealMillennium() Add one millennium to the instance (using timestamp). - * @method CarbonInterface subRealMillennia(int $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). - * @method CarbonInterface subRealMillennium() Sub one millennium to the instance (using timestamp). - * @method CarbonPeriod millenniaUntil($endDate = null, int $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. - * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. - * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. - * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. - * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. - * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. - * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. - * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. - * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. - * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. - * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. - * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. - * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. - * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. - * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. - * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. - * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. - * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. - * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. - * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. - * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. - * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. - * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. - * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. - * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. - * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. - * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. - * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. - * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. - * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. - * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. - * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. - * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. - * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. - * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. - * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. - * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. - * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. - * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. - * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. - * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. - * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. - * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. - * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. - * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. - * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. - * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. - * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. - * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. - * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. - * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. - * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. - * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. - * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. - * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. - * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. - * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. - * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) - * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method bool isUtc() Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + * @method bool isLocal() Check if the current instance has non-UTC timezone. + * @method bool isValid() Check if the current instance is a valid date. + * @method bool isDST() Check if the current instance is in a daylight saving time. + * @method bool isSunday() Checks if the instance day is sunday. + * @method bool isMonday() Checks if the instance day is monday. + * @method bool isTuesday() Checks if the instance day is tuesday. + * @method bool isWednesday() Checks if the instance day is wednesday. + * @method bool isThursday() Checks if the instance day is thursday. + * @method bool isFriday() Checks if the instance day is friday. + * @method bool isSaturday() Checks if the instance day is saturday. + * @method bool isSameYear(DateTimeInterface|string $date) Checks if the given date is in the same year as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentYear() Checks if the instance is in the same year as the current moment. + * @method bool isNextYear() Checks if the instance is in the same year as the current moment next year. + * @method bool isLastYear() Checks if the instance is in the same year as the current moment last year. + * @method bool isCurrentMonth() Checks if the instance is in the same month as the current moment. + * @method bool isNextMonth() Checks if the instance is in the same month as the current moment next month. + * @method bool isLastMonth() Checks if the instance is in the same month as the current moment last month. + * @method bool isSameWeek(DateTimeInterface|string $date) Checks if the given date is in the same week as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentWeek() Checks if the instance is in the same week as the current moment. + * @method bool isNextWeek() Checks if the instance is in the same week as the current moment next week. + * @method bool isLastWeek() Checks if the instance is in the same week as the current moment last week. + * @method bool isSameDay(DateTimeInterface|string $date) Checks if the given date is in the same day as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDay() Checks if the instance is in the same day as the current moment. + * @method bool isNextDay() Checks if the instance is in the same day as the current moment next day. + * @method bool isLastDay() Checks if the instance is in the same day as the current moment last day. + * @method bool isSameHour(DateTimeInterface|string $date) Checks if the given date is in the same hour as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentHour() Checks if the instance is in the same hour as the current moment. + * @method bool isNextHour() Checks if the instance is in the same hour as the current moment next hour. + * @method bool isLastHour() Checks if the instance is in the same hour as the current moment last hour. + * @method bool isSameMinute(DateTimeInterface|string $date) Checks if the given date is in the same minute as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMinute() Checks if the instance is in the same minute as the current moment. + * @method bool isNextMinute() Checks if the instance is in the same minute as the current moment next minute. + * @method bool isLastMinute() Checks if the instance is in the same minute as the current moment last minute. + * @method bool isSameSecond(DateTimeInterface|string $date) Checks if the given date is in the same second as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentSecond() Checks if the instance is in the same second as the current moment. + * @method bool isNextSecond() Checks if the instance is in the same second as the current moment next second. + * @method bool isLastSecond() Checks if the instance is in the same second as the current moment last second. + * @method bool isSameMilli(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMilli() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMilli() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMilli() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMillisecond(DateTimeInterface|string $date) Checks if the given date is in the same millisecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillisecond() Checks if the instance is in the same millisecond as the current moment. + * @method bool isNextMillisecond() Checks if the instance is in the same millisecond as the current moment next millisecond. + * @method bool isLastMillisecond() Checks if the instance is in the same millisecond as the current moment last millisecond. + * @method bool isSameMicro(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicro() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicro() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicro() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameMicrosecond(DateTimeInterface|string $date) Checks if the given date is in the same microsecond as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMicrosecond() Checks if the instance is in the same microsecond as the current moment. + * @method bool isNextMicrosecond() Checks if the instance is in the same microsecond as the current moment next microsecond. + * @method bool isLastMicrosecond() Checks if the instance is in the same microsecond as the current moment last microsecond. + * @method bool isSameDecade(DateTimeInterface|string $date) Checks if the given date is in the same decade as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentDecade() Checks if the instance is in the same decade as the current moment. + * @method bool isNextDecade() Checks if the instance is in the same decade as the current moment next decade. + * @method bool isLastDecade() Checks if the instance is in the same decade as the current moment last decade. + * @method bool isSameCentury(DateTimeInterface|string $date) Checks if the given date is in the same century as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentCentury() Checks if the instance is in the same century as the current moment. + * @method bool isNextCentury() Checks if the instance is in the same century as the current moment next century. + * @method bool isLastCentury() Checks if the instance is in the same century as the current moment last century. + * @method bool isSameMillennium(DateTimeInterface|string $date) Checks if the given date is in the same millennium as the instance. If null passed, compare to now (with the same timezone). + * @method bool isCurrentMillennium() Checks if the instance is in the same millennium as the current moment. + * @method bool isNextMillennium() Checks if the instance is in the same millennium as the current moment next millennium. + * @method bool isLastMillennium() Checks if the instance is in the same millennium as the current moment last millennium. + * @method bool isCurrentQuarter() Checks if the instance is in the same quarter as the current moment. + * @method bool isNextQuarter() Checks if the instance is in the same quarter as the current moment next quarter. + * @method bool isLastQuarter() Checks if the instance is in the same quarter as the current moment last quarter. + * @method CarbonInterface years(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface year(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface setYears(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface setYear(int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance year to the given value. + * @method CarbonInterface months(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface month(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface setMonths(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface setMonth(Month|int $value, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Set current instance month to the given value. + * @method CarbonInterface days(int $value) Set current instance day to the given value. + * @method CarbonInterface day(int $value) Set current instance day to the given value. + * @method CarbonInterface setDays(int $value) Set current instance day to the given value. + * @method CarbonInterface setDay(int $value) Set current instance day to the given value. + * @method CarbonInterface hours(int $value) Set current instance hour to the given value. + * @method CarbonInterface hour(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHours(int $value) Set current instance hour to the given value. + * @method CarbonInterface setHour(int $value) Set current instance hour to the given value. + * @method CarbonInterface minutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface minute(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinutes(int $value) Set current instance minute to the given value. + * @method CarbonInterface setMinute(int $value) Set current instance minute to the given value. + * @method CarbonInterface seconds(int $value) Set current instance second to the given value. + * @method CarbonInterface second(int $value) Set current instance second to the given value. + * @method CarbonInterface setSeconds(int $value) Set current instance second to the given value. + * @method CarbonInterface setSecond(int $value) Set current instance second to the given value. + * @method CarbonInterface millis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillis(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilli(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface milliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface millisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMilliseconds(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface setMillisecond(int $value) Set current instance millisecond to the given value. + * @method CarbonInterface micros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface micro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicros(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicro(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microseconds(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface microsecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface setMicroseconds(int $value) Set current instance microsecond to the given value. + * @method self setMicrosecond(int $value) Set current instance microsecond to the given value. + * @method CarbonInterface addYears(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addYear() Add one year to the instance (using date interval). + * @method CarbonInterface subYears(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subYear() Sub one year to the instance (using date interval). + * @method CarbonInterface addYearsWithOverflow(int|float $value = 1) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearWithOverflow() Add one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearsWithOverflow(int|float $value = 1) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subYearWithOverflow() Sub one year to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithoutOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithoutOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearWithNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearWithNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addYearNoOverflow() Add one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub years (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subYearNoOverflow() Sub one year to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonths(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMonth() Add one month to the instance (using date interval). + * @method CarbonInterface subMonths(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMonth() Sub one month to the instance (using date interval). + * @method CarbonInterface addMonthsWithOverflow(int|float $value = 1) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthWithOverflow() Add one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthsWithOverflow(int|float $value = 1) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMonthWithOverflow() Sub one month to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithoutOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithoutOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthWithNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthWithNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMonthNoOverflow() Add one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthsNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub months (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMonthNoOverflow() Sub one month to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDay() Add one day to the instance (using date interval). + * @method CarbonInterface subDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDay() Sub one day to the instance (using date interval). + * @method CarbonInterface addHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addHour() Add one hour to the instance (using date interval). + * @method CarbonInterface subHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subHour() Sub one hour to the instance (using date interval). + * @method CarbonInterface addMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMinute() Add one minute to the instance (using date interval). + * @method CarbonInterface subMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMinute() Sub one minute to the instance (using date interval). + * @method CarbonInterface addSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addSecond() Add one second to the instance (using date interval). + * @method CarbonInterface subSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subSecond() Sub one second to the instance (using date interval). + * @method CarbonInterface addMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMilli() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMilli() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillisecond() Add one millisecond to the instance (using date interval). + * @method CarbonInterface subMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillisecond() Sub one millisecond to the instance (using date interval). + * @method CarbonInterface addMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicro() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicro() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMicrosecond() Add one microsecond to the instance (using date interval). + * @method CarbonInterface subMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMicrosecond() Sub one microsecond to the instance (using date interval). + * @method CarbonInterface addMillennia(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addMillennium() Add one millennium to the instance (using date interval). + * @method CarbonInterface subMillennia(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subMillennium() Sub one millennium to the instance (using date interval). + * @method CarbonInterface addMillenniaWithOverflow(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniumWithOverflow() Add one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniaWithOverflow(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subMillenniumWithOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithoutOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithoutOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumWithNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumWithNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addMillenniumNoOverflow() Add one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniaNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub millennia (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subMillenniumNoOverflow() Sub one millennium to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturies(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addCentury() Add one century to the instance (using date interval). + * @method CarbonInterface subCenturies(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subCentury() Sub one century to the instance (using date interval). + * @method CarbonInterface addCenturiesWithOverflow(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturyWithOverflow() Add one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturiesWithOverflow(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subCenturyWithOverflow() Sub one century to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithoutOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithoutOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyWithNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyWithNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addCenturyNoOverflow() Add one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturiesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub centuries (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subCenturyNoOverflow() Sub one century to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecades(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addDecade() Add one decade to the instance (using date interval). + * @method CarbonInterface subDecades(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subDecade() Sub one decade to the instance (using date interval). + * @method CarbonInterface addDecadesWithOverflow(int|float $value = 1) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadeWithOverflow() Add one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadesWithOverflow(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subDecadeWithOverflow() Sub one decade to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithoutOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithoutOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeWithNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeWithNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addDecadeNoOverflow() Add one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadesNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub decades (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subDecadeNoOverflow() Sub one decade to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarters(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addQuarter() Add one quarter to the instance (using date interval). + * @method CarbonInterface subQuarters(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subQuarter() Sub one quarter to the instance (using date interval). + * @method CarbonInterface addQuartersWithOverflow(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuarterWithOverflow() Add one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuartersWithOverflow(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface subQuarterWithOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly allowed. + * @method CarbonInterface addQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithoutOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithoutOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithoutOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterWithNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersWithNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterWithNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Add quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addQuarterNoOverflow() Add one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuartersNoOverflow(int|float $value = 1, ?int $anchorDay = null) Sub quarters (the $value count passed in) to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface subQuarterNoOverflow() Sub one quarter to the instance (using date interval) with overflow explicitly forbidden. + * @method CarbonInterface addWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeek() Add one week to the instance (using date interval). + * @method CarbonInterface subWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeek() Sub one week to the instance (using date interval). + * @method CarbonInterface addWeekdays(int|float $value = 1) Add weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface addWeekday() Add one weekday to the instance (using date interval). + * @method CarbonInterface subWeekdays(int|float $value = 1) Sub weekdays (the $value count passed in) to the instance (using date interval). + * @method CarbonInterface subWeekday() Sub one weekday to the instance (using date interval). + * @method CarbonInterface addUTCMicros(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMicro() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subUTCMicros(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMicro() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicros(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method CarbonInterface addUTCMicroseconds(int|float $value = 1) Add microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMicrosecond() Add one microsecond to the instance (using timestamp). + * @method CarbonInterface subUTCMicroseconds(int|float $value = 1) Sub microseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMicrosecond() Sub one microsecond to the instance (using timestamp). + * @method CarbonPeriod microsecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each microsecond or every X microseconds if a factor is given. + * @method float diffInUTCMicroseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of microseconds. + * @method CarbonInterface addUTCMillis(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMilli() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subUTCMillis(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMilli() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMillis(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method CarbonInterface addUTCMilliseconds(int|float $value = 1) Add milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMillisecond() Add one millisecond to the instance (using timestamp). + * @method CarbonInterface subUTCMilliseconds(int|float $value = 1) Sub milliseconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMillisecond() Sub one millisecond to the instance (using timestamp). + * @method CarbonPeriod millisecondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millisecond or every X milliseconds if a factor is given. + * @method float diffInUTCMilliseconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of milliseconds. + * @method CarbonInterface addUTCSeconds(int|float $value = 1) Add seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCSecond() Add one second to the instance (using timestamp). + * @method CarbonInterface subUTCSeconds(int|float $value = 1) Sub seconds (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCSecond() Sub one second to the instance (using timestamp). + * @method CarbonPeriod secondsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each second or every X seconds if a factor is given. + * @method float diffInUTCSeconds(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of seconds. + * @method CarbonInterface addUTCMinutes(int|float $value = 1) Add minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMinute() Add one minute to the instance (using timestamp). + * @method CarbonInterface subUTCMinutes(int|float $value = 1) Sub minutes (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMinute() Sub one minute to the instance (using timestamp). + * @method CarbonPeriod minutesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each minute or every X minutes if a factor is given. + * @method float diffInUTCMinutes(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of minutes. + * @method CarbonInterface addUTCHours(int|float $value = 1) Add hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCHour() Add one hour to the instance (using timestamp). + * @method CarbonInterface subUTCHours(int|float $value = 1) Sub hours (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCHour() Sub one hour to the instance (using timestamp). + * @method CarbonPeriod hoursUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each hour or every X hours if a factor is given. + * @method float diffInUTCHours(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of hours. + * @method CarbonInterface addUTCDays(int|float $value = 1) Add days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCDay() Add one day to the instance (using timestamp). + * @method CarbonInterface subUTCDays(int|float $value = 1) Sub days (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCDay() Sub one day to the instance (using timestamp). + * @method CarbonPeriod daysUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each day or every X days if a factor is given. + * @method float diffInUTCDays(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of days. + * @method CarbonInterface addUTCWeeks(int|float $value = 1) Add weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCWeek() Add one week to the instance (using timestamp). + * @method CarbonInterface subUTCWeeks(int|float $value = 1) Sub weeks (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCWeek() Sub one week to the instance (using timestamp). + * @method CarbonPeriod weeksUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each week or every X weeks if a factor is given. + * @method float diffInUTCWeeks(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of weeks. + * @method CarbonInterface addUTCMonths(int|float $value = 1) Add months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMonth() Add one month to the instance (using timestamp). + * @method CarbonInterface subUTCMonths(int|float $value = 1) Sub months (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMonth() Sub one month to the instance (using timestamp). + * @method CarbonPeriod monthsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each month or every X months if a factor is given. + * @method float diffInUTCMonths(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of months. + * @method CarbonInterface addUTCQuarters(int|float $value = 1) Add quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCQuarter() Add one quarter to the instance (using timestamp). + * @method CarbonInterface subUTCQuarters(int|float $value = 1) Sub quarters (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCQuarter() Sub one quarter to the instance (using timestamp). + * @method CarbonPeriod quartersUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each quarter or every X quarters if a factor is given. + * @method float diffInUTCQuarters(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of quarters. + * @method CarbonInterface addUTCYears(int|float $value = 1) Add years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCYear() Add one year to the instance (using timestamp). + * @method CarbonInterface subUTCYears(int|float $value = 1) Sub years (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCYear() Sub one year to the instance (using timestamp). + * @method CarbonPeriod yearsUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each year or every X years if a factor is given. + * @method float diffInUTCYears(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of years. + * @method CarbonInterface addUTCDecades(int|float $value = 1) Add decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCDecade() Add one decade to the instance (using timestamp). + * @method CarbonInterface subUTCDecades(int|float $value = 1) Sub decades (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCDecade() Sub one decade to the instance (using timestamp). + * @method CarbonPeriod decadesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each decade or every X decades if a factor is given. + * @method float diffInUTCDecades(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of decades. + * @method CarbonInterface addUTCCenturies(int|float $value = 1) Add centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCCentury() Add one century to the instance (using timestamp). + * @method CarbonInterface subUTCCenturies(int|float $value = 1) Sub centuries (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCCentury() Sub one century to the instance (using timestamp). + * @method CarbonPeriod centuriesUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each century or every X centuries if a factor is given. + * @method float diffInUTCCenturies(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of centuries. + * @method CarbonInterface addUTCMillennia(int|float $value = 1) Add millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface addUTCMillennium() Add one millennium to the instance (using timestamp). + * @method CarbonInterface subUTCMillennia(int|float $value = 1) Sub millennia (the $value count passed in) to the instance (using timestamp). + * @method CarbonInterface subUTCMillennium() Sub one millennium to the instance (using timestamp). + * @method CarbonPeriod millenniaUntil($endDate = null, int|float $factor = 1) Return an iterable period from current date to given end (string, DateTime or Carbon instance) for each millennium or every X millennia if a factor is given. + * @method float diffInUTCMillennia(DateTimeInterface|string|null $date, bool $absolute = false) Convert current and given date in UTC timezone and return a floating number of millennia. + * @method CarbonInterface roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function. + * @method CarbonInterface floorYear(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface floorYears(float $precision = 1) Truncate the current instance year with given precision. + * @method CarbonInterface ceilYear(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface ceilYears(float $precision = 1) Ceil the current instance year with given precision. + * @method CarbonInterface roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function. + * @method CarbonInterface floorMonth(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface floorMonths(float $precision = 1) Truncate the current instance month with given precision. + * @method CarbonInterface ceilMonth(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface ceilMonths(float $precision = 1) Ceil the current instance month with given precision. + * @method CarbonInterface roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function. + * @method CarbonInterface floorDay(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface floorDays(float $precision = 1) Truncate the current instance day with given precision. + * @method CarbonInterface ceilDay(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface ceilDays(float $precision = 1) Ceil the current instance day with given precision. + * @method CarbonInterface roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function. + * @method CarbonInterface floorHour(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface floorHours(float $precision = 1) Truncate the current instance hour with given precision. + * @method CarbonInterface ceilHour(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface ceilHours(float $precision = 1) Ceil the current instance hour with given precision. + * @method CarbonInterface roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function. + * @method CarbonInterface floorMinute(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface floorMinutes(float $precision = 1) Truncate the current instance minute with given precision. + * @method CarbonInterface ceilMinute(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision. + * @method CarbonInterface roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function. + * @method CarbonInterface floorSecond(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface floorSeconds(float $precision = 1) Truncate the current instance second with given precision. + * @method CarbonInterface ceilSecond(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface ceilSeconds(float $precision = 1) Ceil the current instance second with given precision. + * @method CarbonInterface roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function. + * @method CarbonInterface floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision. + * @method CarbonInterface ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision. + * @method CarbonInterface roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function. + * @method CarbonInterface floorCentury(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface floorCenturies(float $precision = 1) Truncate the current instance century with given precision. + * @method CarbonInterface ceilCentury(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface ceilCenturies(float $precision = 1) Ceil the current instance century with given precision. + * @method CarbonInterface roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function. + * @method CarbonInterface floorDecade(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface floorDecades(float $precision = 1) Truncate the current instance decade with given precision. + * @method CarbonInterface ceilDecade(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface ceilDecades(float $precision = 1) Ceil the current instance decade with given precision. + * @method CarbonInterface roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function. + * @method CarbonInterface floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision. + * @method CarbonInterface ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision. + * @method CarbonInterface roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function. + * @method CarbonInterface floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision. + * @method CarbonInterface ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision. + * @method CarbonInterface roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function. + * @method CarbonInterface floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision. + * @method CarbonInterface ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method CarbonInterface ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision. + * @method string shortAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longAbsoluteDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Absolute' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'Relative' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToNowDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToNow' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string shortRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (short format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method string longRelativeToOtherDiffForHumans(DateTimeInterface $other = null, int $parts = 1) Get the difference (long format, 'RelativeToOther' mode) in a human readable format in the current locale. ($other and $parts parameters can be swapped.) + * @method int centuriesInMillennium() Return the number of centuries contained in the current millennium + * @method int|static centuryOfMillennium(?int $century = null) Return the value of the century starting from the beginning of the current millennium when called with no parameters, change the current century when called with an integer value + * @method int|static dayOfCentury(?int $day = null) Return the value of the day starting from the beginning of the current century when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfDecade(?int $day = null) Return the value of the day starting from the beginning of the current decade when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMillennium(?int $day = null) Return the value of the day starting from the beginning of the current millennium when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfMonth(?int $day = null) Return the value of the day starting from the beginning of the current month when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfQuarter(?int $day = null) Return the value of the day starting from the beginning of the current quarter when called with no parameters, change the current day when called with an integer value + * @method int|static dayOfWeek(?int $day = null) Return the value of the day starting from the beginning of the current week when called with no parameters, change the current day when called with an integer value + * @method int daysInCentury() Return the number of days contained in the current century + * @method int daysInDecade() Return the number of days contained in the current decade + * @method int daysInMillennium() Return the number of days contained in the current millennium + * @method int daysInMonth() Return the number of days contained in the current month + * @method int daysInQuarter() Return the number of days contained in the current quarter + * @method int daysInWeek() Return the number of days contained in the current week + * @method int daysInYear() Return the number of days contained in the current year + * @method int|static decadeOfCentury(?int $decade = null) Return the value of the decade starting from the beginning of the current century when called with no parameters, change the current decade when called with an integer value + * @method int|static decadeOfMillennium(?int $decade = null) Return the value of the decade starting from the beginning of the current millennium when called with no parameters, change the current decade when called with an integer value + * @method int decadesInCentury() Return the number of decades contained in the current century + * @method int decadesInMillennium() Return the number of decades contained in the current millennium + * @method int|static hourOfCentury(?int $hour = null) Return the value of the hour starting from the beginning of the current century when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDay(?int $hour = null) Return the value of the hour starting from the beginning of the current day when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfDecade(?int $hour = null) Return the value of the hour starting from the beginning of the current decade when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMillennium(?int $hour = null) Return the value of the hour starting from the beginning of the current millennium when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfMonth(?int $hour = null) Return the value of the hour starting from the beginning of the current month when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfQuarter(?int $hour = null) Return the value of the hour starting from the beginning of the current quarter when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfWeek(?int $hour = null) Return the value of the hour starting from the beginning of the current week when called with no parameters, change the current hour when called with an integer value + * @method int|static hourOfYear(?int $hour = null) Return the value of the hour starting from the beginning of the current year when called with no parameters, change the current hour when called with an integer value + * @method int hoursInCentury() Return the number of hours contained in the current century + * @method int hoursInDay() Return the number of hours contained in the current day + * @method int hoursInDecade() Return the number of hours contained in the current decade + * @method int hoursInMillennium() Return the number of hours contained in the current millennium + * @method int hoursInMonth() Return the number of hours contained in the current month + * @method int hoursInQuarter() Return the number of hours contained in the current quarter + * @method int hoursInWeek() Return the number of hours contained in the current week + * @method int hoursInYear() Return the number of hours contained in the current year + * @method int|static microsecondOfCentury(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current century when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDay(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current day when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfDecade(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current decade when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfHour(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current hour when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillennium(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millennium when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMillisecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current millisecond when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMinute(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current minute when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfMonth(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current month when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfQuarter(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current quarter when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfSecond(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current second when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfWeek(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current week when called with no parameters, change the current microsecond when called with an integer value + * @method int|static microsecondOfYear(?int $microsecond = null) Return the value of the microsecond starting from the beginning of the current year when called with no parameters, change the current microsecond when called with an integer value + * @method int microsecondsInCentury() Return the number of microseconds contained in the current century + * @method int microsecondsInDay() Return the number of microseconds contained in the current day + * @method int microsecondsInDecade() Return the number of microseconds contained in the current decade + * @method int microsecondsInHour() Return the number of microseconds contained in the current hour + * @method int microsecondsInMillennium() Return the number of microseconds contained in the current millennium + * @method int microsecondsInMillisecond() Return the number of microseconds contained in the current millisecond + * @method int microsecondsInMinute() Return the number of microseconds contained in the current minute + * @method int microsecondsInMonth() Return the number of microseconds contained in the current month + * @method int microsecondsInQuarter() Return the number of microseconds contained in the current quarter + * @method int microsecondsInSecond() Return the number of microseconds contained in the current second + * @method int microsecondsInWeek() Return the number of microseconds contained in the current week + * @method int microsecondsInYear() Return the number of microseconds contained in the current year + * @method int|static millisecondOfCentury(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current century when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDay(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current day when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfDecade(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current decade when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfHour(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current hour when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMillennium(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current millennium when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMinute(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current minute when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfMonth(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current month when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfQuarter(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current quarter when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfSecond(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current second when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfWeek(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current week when called with no parameters, change the current millisecond when called with an integer value + * @method int|static millisecondOfYear(?int $millisecond = null) Return the value of the millisecond starting from the beginning of the current year when called with no parameters, change the current millisecond when called with an integer value + * @method int millisecondsInCentury() Return the number of milliseconds contained in the current century + * @method int millisecondsInDay() Return the number of milliseconds contained in the current day + * @method int millisecondsInDecade() Return the number of milliseconds contained in the current decade + * @method int millisecondsInHour() Return the number of milliseconds contained in the current hour + * @method int millisecondsInMillennium() Return the number of milliseconds contained in the current millennium + * @method int millisecondsInMinute() Return the number of milliseconds contained in the current minute + * @method int millisecondsInMonth() Return the number of milliseconds contained in the current month + * @method int millisecondsInQuarter() Return the number of milliseconds contained in the current quarter + * @method int millisecondsInSecond() Return the number of milliseconds contained in the current second + * @method int millisecondsInWeek() Return the number of milliseconds contained in the current week + * @method int millisecondsInYear() Return the number of milliseconds contained in the current year + * @method int|static minuteOfCentury(?int $minute = null) Return the value of the minute starting from the beginning of the current century when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDay(?int $minute = null) Return the value of the minute starting from the beginning of the current day when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfDecade(?int $minute = null) Return the value of the minute starting from the beginning of the current decade when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfHour(?int $minute = null) Return the value of the minute starting from the beginning of the current hour when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMillennium(?int $minute = null) Return the value of the minute starting from the beginning of the current millennium when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfMonth(?int $minute = null) Return the value of the minute starting from the beginning of the current month when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfQuarter(?int $minute = null) Return the value of the minute starting from the beginning of the current quarter when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfWeek(?int $minute = null) Return the value of the minute starting from the beginning of the current week when called with no parameters, change the current minute when called with an integer value + * @method int|static minuteOfYear(?int $minute = null) Return the value of the minute starting from the beginning of the current year when called with no parameters, change the current minute when called with an integer value + * @method int minutesInCentury() Return the number of minutes contained in the current century + * @method int minutesInDay() Return the number of minutes contained in the current day + * @method int minutesInDecade() Return the number of minutes contained in the current decade + * @method int minutesInHour() Return the number of minutes contained in the current hour + * @method int minutesInMillennium() Return the number of minutes contained in the current millennium + * @method int minutesInMonth() Return the number of minutes contained in the current month + * @method int minutesInQuarter() Return the number of minutes contained in the current quarter + * @method int minutesInWeek() Return the number of minutes contained in the current week + * @method int minutesInYear() Return the number of minutes contained in the current year + * @method int|static monthOfCentury(?int $month = null) Return the value of the month starting from the beginning of the current century when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfDecade(?int $month = null) Return the value of the month starting from the beginning of the current decade when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfMillennium(?int $month = null) Return the value of the month starting from the beginning of the current millennium when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfQuarter(?int $month = null) Return the value of the month starting from the beginning of the current quarter when called with no parameters, change the current month when called with an integer value + * @method int|static monthOfYear(?int $month = null) Return the value of the month starting from the beginning of the current year when called with no parameters, change the current month when called with an integer value + * @method int monthsInCentury() Return the number of months contained in the current century + * @method int monthsInDecade() Return the number of months contained in the current decade + * @method int monthsInMillennium() Return the number of months contained in the current millennium + * @method int monthsInQuarter() Return the number of months contained in the current quarter + * @method int monthsInYear() Return the number of months contained in the current year + * @method int|static quarterOfCentury(?int $quarter = null) Return the value of the quarter starting from the beginning of the current century when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfDecade(?int $quarter = null) Return the value of the quarter starting from the beginning of the current decade when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfMillennium(?int $quarter = null) Return the value of the quarter starting from the beginning of the current millennium when called with no parameters, change the current quarter when called with an integer value + * @method int|static quarterOfYear(?int $quarter = null) Return the value of the quarter starting from the beginning of the current year when called with no parameters, change the current quarter when called with an integer value + * @method int quartersInCentury() Return the number of quarters contained in the current century + * @method int quartersInDecade() Return the number of quarters contained in the current decade + * @method int quartersInMillennium() Return the number of quarters contained in the current millennium + * @method int quartersInYear() Return the number of quarters contained in the current year + * @method int|static secondOfCentury(?int $second = null) Return the value of the second starting from the beginning of the current century when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDay(?int $second = null) Return the value of the second starting from the beginning of the current day when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfDecade(?int $second = null) Return the value of the second starting from the beginning of the current decade when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfHour(?int $second = null) Return the value of the second starting from the beginning of the current hour when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMillennium(?int $second = null) Return the value of the second starting from the beginning of the current millennium when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMinute(?int $second = null) Return the value of the second starting from the beginning of the current minute when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfMonth(?int $second = null) Return the value of the second starting from the beginning of the current month when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfQuarter(?int $second = null) Return the value of the second starting from the beginning of the current quarter when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfWeek(?int $second = null) Return the value of the second starting from the beginning of the current week when called with no parameters, change the current second when called with an integer value + * @method int|static secondOfYear(?int $second = null) Return the value of the second starting from the beginning of the current year when called with no parameters, change the current second when called with an integer value + * @method int secondsInCentury() Return the number of seconds contained in the current century + * @method int secondsInDay() Return the number of seconds contained in the current day + * @method int secondsInDecade() Return the number of seconds contained in the current decade + * @method int secondsInHour() Return the number of seconds contained in the current hour + * @method int secondsInMillennium() Return the number of seconds contained in the current millennium + * @method int secondsInMinute() Return the number of seconds contained in the current minute + * @method int secondsInMonth() Return the number of seconds contained in the current month + * @method int secondsInQuarter() Return the number of seconds contained in the current quarter + * @method int secondsInWeek() Return the number of seconds contained in the current week + * @method int secondsInYear() Return the number of seconds contained in the current year + * @method int|static weekOfCentury(?int $week = null) Return the value of the week starting from the beginning of the current century when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfDecade(?int $week = null) Return the value of the week starting from the beginning of the current decade when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMillennium(?int $week = null) Return the value of the week starting from the beginning of the current millennium when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfMonth(?int $week = null) Return the value of the week starting from the beginning of the current month when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfQuarter(?int $week = null) Return the value of the week starting from the beginning of the current quarter when called with no parameters, change the current week when called with an integer value + * @method int|static weekOfYear(?int $week = null) Return the value of the week starting from the beginning of the current year when called with no parameters, change the current week when called with an integer value + * @method int weeksInCentury() Return the number of weeks contained in the current century + * @method int weeksInDecade() Return the number of weeks contained in the current decade + * @method int weeksInMillennium() Return the number of weeks contained in the current millennium + * @method int weeksInMonth() Return the number of weeks contained in the current month + * @method int weeksInQuarter() Return the number of weeks contained in the current quarter + * @method int|static yearOfCentury(?int $year = null) Return the value of the year starting from the beginning of the current century when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfDecade(?int $year = null) Return the value of the year starting from the beginning of the current decade when called with no parameters, change the current year when called with an integer value + * @method int|static yearOfMillennium(?int $year = null) Return the value of the year starting from the beginning of the current millennium when called with no parameters, change the current year when called with an integer value + * @method int yearsInCentury() Return the number of years contained in the current century + * @method int yearsInDecade() Return the number of years contained in the current decade + * @method int yearsInMillennium() Return the number of years contained in the current millennium + * @method static plus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Add given amount of time to the current date. + * @method static minus(int $years = 0, int $months = 0, int|float $weeks = 0, int|float $days = 0, int|float $hours = 0, int|float $minutes = 0, int|float $seconds = 0, int|float $microseconds = 0, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null) Subtract given amount of time to the current date. * * */ @@ -531,6 +872,7 @@ trait Date use Converter; use Creator; use Difference; + use Eras; use Macro; use MagicParameter; use Modifiers; @@ -566,13 +908,6 @@ trait Date CarbonInterface::SATURDAY => 'Saturday', ]; - /** - * Will UTF8 encoding be used to print localized date/time ? - * - * @var bool - */ - protected static $utf8 = false; - /** * List of unit and magic methods associated as doc-comments. * @@ -614,37 +949,32 @@ trait Date /** * Creates a DateTimeZone from a string, DateTimeZone or integer offset. * - * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it. - * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages. + * @param DateTimeZone|string|int|false|null $object original value to get CarbonTimeZone from it. + * @param DateTimeZone|string|int|false|null $objectDump dump of the object for error messages. * * @throws InvalidTimeZoneException * - * @return CarbonTimeZone|false + * @return CarbonTimeZone|null */ - protected static function safeCreateDateTimeZone($object, $objectDump = null) - { + protected static function safeCreateDateTimeZone( + DateTimeZone|string|int|false|null $object, + DateTimeZone|string|int|false|null $objectDump = null, + ): ?CarbonTimeZone { return CarbonTimeZone::instance($object, $objectDump); } /** * Get the TimeZone associated with the Carbon instance (as CarbonTimeZone). * - * @return CarbonTimeZone - * * @link https://php.net/manual/en/datetime.gettimezone.php */ - #[ReturnTypeWillChange] - public function getTimezone() + public function getTimezone(): CarbonTimeZone { - return CarbonTimeZone::instance(parent::getTimezone()); + return $this->transmitFactory(fn () => CarbonTimeZone::instance(parent::getTimezone())); } /** * List of minimum and maximums for each unit. - * - * @param int $daysInMonth - * - * @return array */ protected static function getRangesByUnit(int $daysInMonth = 31): array { @@ -694,7 +1024,7 @@ trait Date * * @return static */ - public function avoidMutation(): self + public function avoidMutation(): static { if ($this instanceof DateTimeImmutable) { return $this; @@ -708,92 +1038,24 @@ trait Date * * @return static */ - public function nowWithSameTz() + public function nowWithSameTz(): static { - return static::now($this->getTimezone()); - } + $timezone = $this->getTimezone(); - /** - * Throws an exception if the given object is not a DateTime and does not implement DateTimeInterface. - * - * @param mixed $date - * @param string|array $other - * - * @throws InvalidTypeException - */ - protected static function expectDateTime($date, $other = []) - { - $message = 'Expected '; - foreach ((array) $other as $expect) { - $message .= "$expect, "; - } - - if (!$date instanceof DateTime && !$date instanceof DateTimeInterface) { - throw new InvalidTypeException( - $message.'DateTime or DateTimeInterface, '. - (\is_object($date) ? \get_class($date) : \gettype($date)).' given' - ); - } + return $this->getClock()?->nowAs(static::class, $timezone) ?? static::now($timezone); } /** * Return the Carbon instance passed through, a now instance in the same timezone * if null given or parse the input if string given. * - * @param Carbon|DateTimeInterface|string|null $date - * - * @return static - */ - protected function resolveCarbon($date = null) - { - if (!$date) { - return $this->nowWithSameTz(); - } - - if (\is_string($date)) { - return static::parse($date, $this->getTimezone()); - } - - static::expectDateTime($date, ['null', 'string']); - - return $date instanceof self ? $date : static::instance($date); - } - - /** - * Return the Carbon instance passed through, a now instance in UTC - * if null given or parse the input if string given (using current timezone - * then switching to UTC). - * - * @param Carbon|DateTimeInterface|string|null $date - * - * @return static - */ - protected function resolveUTC($date = null): self - { - if (!$date) { - return static::now('UTC'); - } - - if (\is_string($date)) { - return static::parse($date, $this->getTimezone())->utc(); - } - - static::expectDateTime($date, ['null', 'string']); - - return $date instanceof self ? $date : static::instance($date)->utc(); - } - - /** - * Return the Carbon instance passed through, a now instance in the same timezone - * if null given or parse the input if string given. - * - * @param Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|DateTimeInterface|string|null $date + * @param Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\Time\Duration|\DatePeriod|DateTimeInterface|string|null $date * * @return static */ public function carbonize($date = null) { - if ($date instanceof DateInterval) { + if ($date instanceof DateInterval || $date instanceof Duration) { return $this->avoidMutation()->add($date); } @@ -809,37 +1071,51 @@ trait Date /////////////////////////////////////////////////////////////////// /** - * Get a part of the Carbon object - * - * @param string $name + * Get a part of the Carbon object. * * @throws UnknownGetterException * * @return string|int|bool|DateTimeZone|null */ - public function __get($name) + public function __get(string $name): mixed { return $this->get($name); } /** - * Get a part of the Carbon object - * - * @param string $name + * Get a part of the Carbon object. * * @throws UnknownGetterException * - * @return string|int|bool|DateTimeZone|null + * @return string|int|bool|DateTimeZone */ - public function get($name) + public function get(Unit|string $name): mixed { + static $localizedFormats = [ + // @property string the day of week in current locale + 'localeDayOfWeek' => 'dddd', + // @property string the abbreviated day of week in current locale + 'shortLocaleDayOfWeek' => 'ddd', + // @property string the month in current locale + 'localeMonth' => 'MMMM', + // @property string the abbreviated month in current locale + 'shortLocaleMonth' => 'MMM', + ]; + + $name = Unit::toName($name); + + if (isset($localizedFormats[$name])) { + return $this->isoFormat($localizedFormats[$name]); + } + static $formats = [ // @property int 'year' => 'Y', // @property int 'yearIso' => 'o', + // @--property-read int + // @--property-write Month|int // @property int - // @call isSameUnit 'month' => 'n', // @property int 'day' => 'j', @@ -853,11 +1129,11 @@ trait Date 'micro' => 'u', // @property int 'microsecond' => 'u', - // @property-read int 0 (for Sunday) through 6 (for Saturday) + // @property int 0 (for Sunday) through 6 (for Saturday) 'dayOfWeek' => 'w', - // @property-read int 1 (for Monday) through 7 (for Sunday) + // @property int 1 (for Monday) through 7 (for Sunday) 'dayOfWeekIso' => 'N', - // @property-read int ISO-8601 week number of year, weeks starting on Monday + // @property int ISO-8601 week number of year, weeks starting on Monday 'weekOfYear' => 'W', // @property-read int number of days in the given month 'daysInMonth' => 't', @@ -875,30 +1151,6 @@ trait Date 'englishMonth' => 'F', // @property string the abbreviated month in English 'shortEnglishMonth' => 'M', - // @property string the day of week in current locale LC_TIME - // @deprecated - // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. - // replacement: Use ->isoFormat('MMM') instead. - // since: 2.55.0 - 'localeDayOfWeek' => '%A', - // @property string the abbreviated day of week in current locale LC_TIME - // @deprecated - // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. - // replacement: Use ->isoFormat('dddd') instead. - // since: 2.55.0 - 'shortLocaleDayOfWeek' => '%a', - // @property string the month in current locale LC_TIME - // @deprecated - // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. - // replacement: Use ->isoFormat('ddd') instead. - // since: 2.55.0 - 'localeMonth' => '%B', - // @property string the abbreviated month in current locale LC_TIME - // @deprecated - // reason: It uses OS language package and strftime() which is deprecated since PHP 8.1. - // replacement: Use ->isoFormat('MMMM') instead. - // since: 2.55.0 - 'shortLocaleMonth' => '%b', // @property-read string $timezoneAbbreviatedName the current timezone abbreviated name 'timezoneAbbreviatedName' => 'T', // @property-read string $tzAbbrName alias of $timezoneAbbreviatedName @@ -907,9 +1159,7 @@ trait Date switch (true) { case isset($formats[$name]): - $format = $formats[$name]; - $method = str_starts_with($format, '%') ? 'formatLocalized' : 'rawFormat'; - $value = $this->$method($format); + $value = $this->rawFormat($formats[$name]); return is_numeric($value) ? (int) $value : $value; @@ -969,7 +1219,7 @@ trait Date case $name === 'isoWeeksInYear': return $this->isoWeeksInYear(); - // @property-read int 1 through 5 + // @property int 1 through 5 case $name === 'weekOfMonth': return (int) ceil($this->day / static::DAYS_PER_WEEK); @@ -979,11 +1229,11 @@ trait Date // @property-read int 0 through 6 case $name === 'firstWeekDay': - return $this->localTranslator ? ($this->getTranslationMessage('first_day_of_week') ?? 0) : static::getWeekStartsAt(); + return (int) $this->getTranslationMessage('first_day_of_week'); // @property-read int 0 through 6 case $name === 'lastWeekDay': - return $this->localTranslator ? (($this->getTranslationMessage('first_day_of_week') ?? 0) + static::DAYS_PER_WEEK - 1) % static::DAYS_PER_WEEK : static::getWeekEndsAt(); + return $this->transmitFactory(fn () => static::weekRotate((int) $this->getTranslationMessage('first_day_of_week'), -1)); // @property int 1 through 366 case $name === 'dayOfYear': @@ -991,14 +1241,13 @@ trait Date // @property-read int 365 or 366 case $name === 'daysInYear': - return $this->isLeapYear() ? 366 : 365; + return static::DAYS_PER_YEAR + ($this->isLeapYear() ? 1 : 0); // @property int does a diffInYears() with default parameters case $name === 'age': - return $this->diffInYears(); + return (int) $this->diffInYears(); // @property-read int the quarter of this instance, 1 - 4 - // @call isSameUnit case $name === 'quarter': return (int) ceil($this->month / static::MONTHS_PER_QUARTER); @@ -1012,6 +1261,7 @@ trait Date case $name === 'century': $factor = 1; $year = $this->year; + if ($year < 0) { $year = -$year; $factor = -1; @@ -1024,6 +1274,7 @@ trait Date case $name === 'millennium': $factor = 1; $year = $this->year; + if ($year < 0) { $year = -$year; $factor = -1; @@ -1055,10 +1306,14 @@ trait Date case $name === 'utc': return $this->getOffset() === 0; + // @--property-write DateTimeZone|string|int $timezone the current timezone + // @--property-write DateTimeZone|string|int $tz alias of $timezone + // @--property-read CarbonTimeZone $timezone the current timezone + // @--property-read CarbonTimeZone $tz alias of $timezone // @property CarbonTimeZone $timezone the current timezone // @property CarbonTimeZone $tz alias of $timezone case $name === 'timezone' || $name === 'tz': - return CarbonTimeZone::instance($this->getTimezone()); + return $this->getTimezone(); // @property-read string $timezoneName the current timezone name // @property-read string $tzName alias of $timezoneName @@ -1069,6 +1324,26 @@ trait Date case $name === 'locale': return $this->getTranslatorLocale(); + case preg_match('/^([a-z]{2,})(In|Of)([A-Z][a-z]+)$/', $name, $match): + [, $firstUnit, $operator, $secondUnit] = $match; + + try { + $start = $this->avoidMutation()->startOf($secondUnit); + $value = $operator === 'Of' + ? (\in_array($firstUnit, [ + // Unit with indexes starting at 1 (other units start at 0) + 'day', + 'week', + 'month', + 'quarter', + ], true) ? 1 : 0) + floor($start->diffInUnit($firstUnit, $this)) + : round($start->diffInUnit($firstUnit, $start->avoidMutation()->add($secondUnit, 1))); + + return (int) $value; + } catch (UnknownUnitException) { + // default to macro + } + default: $macro = $this->getLocalMacro('get'.ucfirst($name)); @@ -1091,7 +1366,7 @@ trait Date { try { $this->__get($name); - } catch (UnknownGetterException | ReflectionException $e) { + } catch (UnknownGetterException | ReflectionException) { return false; } @@ -1110,7 +1385,11 @@ trait Date */ public function __set($name, $value) { - if ($this->constructedObjectId === spl_object_hash($this)) { + if ($this->constructedObjectId === spl_object_id($this)) { + if ($this->isImmutable()) { + throw ImmutableException::fromClass(static::class); + } + $this->set($name, $value); return; @@ -1120,29 +1399,26 @@ trait Date } /** - * Set a part of the Carbon object - * - * @param string|array $name - * @param string|int|DateTimeZone $value + * Set one or multiple components of the Carbon object. * * @throws ImmutableException|UnknownSetterException * - * @return $this + * @return static */ - public function set($name, $value = null) + public function set(Unit|array|string $name, DateTimeZone|Month|string|int|float|null $value = null): static { - if ($this->isImmutable()) { - throw new ImmutableException(sprintf('%s class', static::class)); - } + $result = $this; if (\is_array($name)) { foreach ($name as $key => $value) { - $this->set($key, $value); + $result = $result->set($key, $value); } - return $this; + return $result; } + $name = Unit::toName($name); + switch ($name) { case 'milliseconds': case 'millisecond': @@ -1155,16 +1431,16 @@ trait Date } while ($value < 0) { - $this->subSecond(); + $result = $result->subSecond(); $value += static::MICROSECONDS_PER_SECOND; } while ($value >= static::MICROSECONDS_PER_SECOND) { - $this->addSecond(); + $result = $result->addSecond(); $value -= static::MICROSECONDS_PER_SECOND; } - $this->modify($this->rawFormat('H:i:s.').str_pad((string) round($value), 6, '0', STR_PAD_LEFT)); + $result = $result->modify($this->rawFormat('H:i:s.').str_pad((string) round($value), 6, '0', STR_PAD_LEFT)); break; @@ -1175,99 +1451,115 @@ trait Date case 'minute': case 'second': [$year, $month, $day, $hour, $minute, $second] = array_map('intval', explode('-', $this->rawFormat('Y-n-j-G-i-s'))); - $$name = $value; - $this->setDateTime($year, $month, $day, $hour, $minute, $second); + $$name = self::monthToInt($value, $name); + $result = $result->setDateTime($year, $month, $day, $hour, $minute, $second); break; case 'week': - $this->week($value); + $result = $result->week($value); break; case 'isoWeek': - $this->isoWeek($value); + $result = $result->isoWeek($value); break; case 'weekYear': - $this->weekYear($value); + $result = $result->weekYear($value); break; case 'isoWeekYear': - $this->isoWeekYear($value); + $result = $result->isoWeekYear($value); break; case 'dayOfYear': - $this->addDays($value - $this->dayOfYear); + $result = $result->addDays($value - $this->dayOfYear); + + break; + + case 'dayOfWeek': + $result = $result->addDays($value - $this->dayOfWeek); + + break; + + case 'dayOfWeekIso': + $result = $result->addDays($value - $this->dayOfWeekIso); break; case 'timestamp': - $this->setTimestamp($value); + $result = $result->setTimestamp($value); break; case 'offset': - $this->setTimezone(static::safeCreateDateTimeZone($value / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR)); + $result = $result->setTimezone(static::safeCreateDateTimeZone($value / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR)); break; case 'offsetMinutes': - $this->setTimezone(static::safeCreateDateTimeZone($value / static::MINUTES_PER_HOUR)); + $result = $result->setTimezone(static::safeCreateDateTimeZone($value / static::MINUTES_PER_HOUR)); break; case 'offsetHours': - $this->setTimezone(static::safeCreateDateTimeZone($value)); + $result = $result->setTimezone(static::safeCreateDateTimeZone($value)); break; case 'timezone': case 'tz': - $this->setTimezone($value); + $result = $result->setTimezone($value); break; default: + if (preg_match('/^([a-z]{2,})Of([A-Z][a-z]+)$/', $name, $match)) { + [, $firstUnit, $secondUnit] = $match; + + try { + $start = $this->avoidMutation()->startOf($secondUnit); + $currentValue = (\in_array($firstUnit, [ + // Unit with indexes starting at 1 (other units start at 0) + 'day', + 'week', + 'month', + 'quarter', + ], true) ? 1 : 0) + (int) floor($start->diffInUnit($firstUnit, $result)); + + // We check $value a posteriori to give precedence to UnknownUnitException + if (!\is_int($value)) { + throw new UnitException("->$name expects integer value"); + } + + $result = $result->addUnit($firstUnit, $value - $currentValue); + + break; + } catch (UnknownUnitException) { + // default to macro + } + } + $macro = $this->getLocalMacro('set'.ucfirst($name)); if ($macro) { - $this->executeCallableWithContext($macro, $value); + $result = $result->executeCallableWithContext($macro, $value) ?? $this; break; } - if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + if ($this->isLocalStrictModeEnabled()) { throw new UnknownSetterException($name); } - $this->$name = $value; + $result->$name = $value; } - return $this; - } - - protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue) - { - $key = $baseKey.$keySuffix; - $standaloneKey = "{$key}_standalone"; - $baseTranslation = $this->getTranslationMessage($key); - - if ($baseTranslation instanceof Closure) { - return $baseTranslation($this, $context, $subKey) ?: $defaultValue; - } - - if ( - $this->getTranslationMessage("$standaloneKey.$subKey") && - (!$context || (($regExp = $this->getTranslationMessage("{$baseKey}_regexp")) && !preg_match($regExp, $context))) - ) { - $key = $standaloneKey; - } - - return $this->getTranslationMessage("$key.$subKey", null, $defaultValue); + return $result; } /** @@ -1276,11 +1568,12 @@ trait Date * @param string|null $context whole format string * @param string $keySuffix "", "_short" or "_min" * @param string|null $defaultValue default value if translation missing - * - * @return string */ - public function getTranslatedDayName($context = null, $keySuffix = '', $defaultValue = null) - { + public function getTranslatedDayName( + ?string $context = null, + string $keySuffix = '', + ?string $defaultValue = null, + ): string { return $this->getTranslatedFormByRegExp('weekdays', $keySuffix, $context, $this->dayOfWeek, $defaultValue ?: $this->englishDayOfWeek); } @@ -1288,10 +1581,8 @@ trait Date * Get the translation of the current short week day name (with context for languages with multiple forms). * * @param string|null $context whole format string - * - * @return string */ - public function getTranslatedShortDayName($context = null) + public function getTranslatedShortDayName(?string $context = null): string { return $this->getTranslatedDayName($context, '_short', $this->shortEnglishDayOfWeek); } @@ -1300,10 +1591,8 @@ trait Date * Get the translation of the current abbreviated week day name (with context for languages with multiple forms). * * @param string|null $context whole format string - * - * @return string */ - public function getTranslatedMinDayName($context = null) + public function getTranslatedMinDayName(?string $context = null): string { return $this->getTranslatedDayName($context, '_min', $this->shortEnglishDayOfWeek); } @@ -1314,11 +1603,12 @@ trait Date * @param string|null $context whole format string * @param string $keySuffix "" or "_short" * @param string|null $defaultValue default value if translation missing - * - * @return string */ - public function getTranslatedMonthName($context = null, $keySuffix = '', $defaultValue = null) - { + public function getTranslatedMonthName( + ?string $context = null, + string $keySuffix = '', + ?string $defaultValue = null, + ): string { return $this->getTranslatedFormByRegExp('months', $keySuffix, $context, $this->month - 1, $defaultValue ?: $this->englishMonth); } @@ -1326,10 +1616,8 @@ trait Date * Get the translation of the current short month day name (with context for languages with multiple forms). * * @param string|null $context whole format string - * - * @return string */ - public function getTranslatedShortMonthName($context = null) + public function getTranslatedShortMonthName(?string $context = null): string { return $this->getTranslatedMonthName($context, '_short', $this->shortEnglishMonth); } @@ -1339,9 +1627,9 @@ trait Date * * @param int|null $value new value for day of year if using as setter. * - * @return static|int + * @return ($value is null ? int : static) */ - public function dayOfYear($value = null) + public function dayOfYear(?int $value = null): static|int { $dayOfYear = $this->dayOfYear; @@ -1351,11 +1639,11 @@ trait Date /** * Get/set the weekday from 0 (Sunday) to 6 (Saturday). * - * @param int|null $value new value for weekday if using as setter. + * @param WeekDay|int|null $value new value for weekday if using as setter. * - * @return static|int + * @return ($value is null ? int : static) */ - public function weekday($value = null) + public function weekday(WeekDay|int|null $value = null): static|int { if ($value === null) { return $this->dayOfWeek; @@ -1364,36 +1652,34 @@ trait Date $firstDay = (int) ($this->getTranslationMessage('first_day_of_week') ?? 0); $dayOfWeek = ($this->dayOfWeek + 7 - $firstDay) % 7; - return $this->addDays((($value + 7 - $firstDay) % 7) - $dayOfWeek); + return $this->addDays(((WeekDay::int($value) + 7 - $firstDay) % 7) - $dayOfWeek); } /** * Get/set the ISO weekday from 1 (Monday) to 7 (Sunday). * - * @param int|null $value new value for weekday if using as setter. + * @param WeekDay|int|null $value new value for weekday if using as setter. * - * @return static|int + * @return ($value is null ? int : static) */ - public function isoWeekday($value = null) + public function isoWeekday(WeekDay|int|null $value = null): static|int { $dayOfWeekIso = $this->dayOfWeekIso; - return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso); + return $value === null ? $dayOfWeekIso : $this->addDays(WeekDay::int($value) - $dayOfWeekIso); } /** * Return the number of days since the start of the week (using the current locale or the first parameter * if explicitly given). * - * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, - * if not provided, start of week is inferred from the locale - * (Sunday for en_US, Monday for de_DE, etc.) - * - * @return int + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) */ - public function getDaysFromStartOfWeek(int $weekStartsAt = null): int + public function getDaysFromStartOfWeek(WeekDay|int|null $weekStartsAt = null): int { - $firstDay = (int) ($weekStartsAt ?? $this->getTranslationMessage('first_day_of_week') ?? 0); + $firstDay = (int) (WeekDay::int($weekStartsAt) ?? $this->getTranslationMessage('first_day_of_week') ?? 0); return ($this->dayOfWeek + 7 - $firstDay) % 7; } @@ -1402,42 +1688,48 @@ trait Date * Set the day (keeping the current time) to the start of the week + the number of days passed as the first * parameter. First day of week is driven by the locale unless explicitly set with the second parameter. * - * @param int $numberOfDays number of days to add after the start of the current week - * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, - * if not provided, start of week is inferred from the locale - * (Sunday for en_US, Monday for de_DE, etc.) - * - * @return static + * @param int $numberOfDays number of days to add after the start of the current week + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week, + * if not provided, start of week is inferred from the locale + * (Sunday for en_US, Monday for de_DE, etc.) */ - public function setDaysFromStartOfWeek(int $numberOfDays, int $weekStartsAt = null) + public function setDaysFromStartOfWeek(int $numberOfDays, WeekDay|int|null $weekStartsAt = null): static { - return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek($weekStartsAt)); + return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek(WeekDay::int($weekStartsAt))); } /** * Set any unit to a new value without overflowing current other unit given. * - * @param string $valueUnit unit name to modify - * @param int $value new value for the input unit - * @param string $overflowUnit unit name to not overflow - * - * @return static + * @param Unit|string $valueUnit unit name to modify + * @param int $value new value for the input unit + * @param Unit|string $overflowUnit unit name to not overflow + * @param ?int $anchorDay set day to this value after modification */ - public function setUnitNoOverflow($valueUnit, $value, $overflowUnit) - { + public function setUnitNoOverflow( + Unit|string $valueUnit, + int $value, + Unit|string $overflowUnit, + ?int $anchorDay = null, + ): static { + $valueUnit = Unit::toName($valueUnit); + $overflowUnit = Unit::toName($overflowUnit); + try { - $original = $this->avoidMutation(); + $start = $this->avoidMutation()->startOf($overflowUnit); + $end = $this->avoidMutation()->endOf($overflowUnit); /** @var static $date */ $date = $this->$valueUnit($value); - $end = $original->avoidMutation()->endOf($overflowUnit); - $start = $original->avoidMutation()->startOf($overflowUnit); + if ($date < $start) { - $date = $date->setDateTimeFrom($start); - } elseif ($date > $end) { - $date = $date->setDateTimeFrom($end); + return $date->mutateIfMutable($start)->setAnchorDayIfNotNull($anchorDay); } - return $date; + if ($date > $end) { + return $date->mutateIfMutable($end)->setAnchorDayIfNotNull($anchorDay); + } + + return $date->setAnchorDayIfNotNull($anchorDay); } catch (BadMethodCallException | ReflectionException $exception) { throw new UnknownUnitException($valueUnit, 0, $exception); } @@ -1446,41 +1738,49 @@ trait Date /** * Add any unit to a new value without overflowing current other unit given. * - * @param string $valueUnit unit name to modify - * @param int $value amount to add to the input unit - * @param string $overflowUnit unit name to not overflow - * - * @return static + * @param Unit|string $valueUnit unit name to modify + * @param int $value amount to add to the input unit + * @param Unit|string $overflowUnit unit name to not overflow + * @param ?int $anchorDay set day to this value after modification */ - public function addUnitNoOverflow($valueUnit, $value, $overflowUnit) - { - return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit + $value, $overflowUnit); + public function addUnitNoOverflow( + Unit|string $valueUnit, + int $value, + Unit|string $overflowUnit, + ?int $anchorDay = null, + ): static { + $valueUnit = Unit::toName($valueUnit); + + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit + $value, $overflowUnit, $anchorDay); } /** * Subtract any unit to a new value without overflowing current other unit given. * - * @param string $valueUnit unit name to modify - * @param int $value amount to subtract to the input unit - * @param string $overflowUnit unit name to not overflow - * - * @return static + * @param Unit|string $valueUnit unit name to modify + * @param int $value amount to subtract to the input unit + * @param Unit|string $overflowUnit unit name to not overflow + * @param ?int $anchorDay set day to this value after modification */ - public function subUnitNoOverflow($valueUnit, $value, $overflowUnit) - { - return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit - $value, $overflowUnit); + public function subUnitNoOverflow( + Unit|string $valueUnit, + int $value, + Unit|string $overflowUnit, + ?int $anchorDay = null, + ): static { + $valueUnit = Unit::toName($valueUnit); + + return $this->setUnitNoOverflow($valueUnit, $this->$valueUnit - $value, $overflowUnit, $anchorDay); } /** * Returns the minutes offset to UTC if no arguments passed, else set the timezone with given minutes shift passed. * - * @param int|null $minuteOffset - * - * @return int|static + * @return ($minuteOffset is null ? int : static) */ - public function utcOffset(int $minuteOffset = null) + public function utcOffset(?int $minuteOffset = null): static|int { - if (\func_num_args() < 1) { + if ($minuteOffset === null) { return $this->offsetMinutes; } @@ -1491,97 +1791,63 @@ trait Date * Set the date with gregorian year, month and day numbers. * * @see https://php.net/manual/en/datetime.setdate.php - * - * @param int $year - * @param int $month - * @param int $day - * - * @return static */ - #[ReturnTypeWillChange] - public function setDate($year, $month, $day) + public function setDate(int $year, int $month, int $day): static { - return parent::setDate((int) $year, (int) $month, (int) $day); + return parent::setDate($year, $month, $day); } /** * Set a date according to the ISO 8601 standard - using weeks and day offsets rather than specific dates. * * @see https://php.net/manual/en/datetime.setisodate.php - * - * @param int $year - * @param int $week - * @param int $day - * - * @return static */ - #[ReturnTypeWillChange] - public function setISODate($year, $week, $day = 1) + public function setISODate(int $year, int $week, int $day = 1): static { - return parent::setISODate((int) $year, (int) $week, (int) $day); + return parent::setISODate($year, $week, $day); } /** * Set the date and time all together. - * - * @param int $year - * @param int $month - * @param int $day - * @param int $hour - * @param int $minute - * @param int $second - * @param int $microseconds - * - * @return static */ - public function setDateTime($year, $month, $day, $hour, $minute, $second = 0, $microseconds = 0) - { - return $this->setDate($year, $month, $day)->setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + public function setDateTime( + int $year, + int $month, + int $day, + int $hour, + int $minute, + int $second = 0, + int $microseconds = 0, + ): static { + return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second, $microseconds); } /** * Resets the current time of the DateTime object to a different time. * * @see https://php.net/manual/en/datetime.settime.php - * - * @param int $hour - * @param int $minute - * @param int $second - * @param int $microseconds - * - * @return static */ - #[ReturnTypeWillChange] - public function setTime($hour, $minute, $second = 0, $microseconds = 0) + public function setTime(int $hour, int $minute, int $second = 0, int $microseconds = 0): static { - return parent::setTime((int) $hour, (int) $minute, (int) $second, (int) $microseconds); + return parent::setTime($hour, $minute, $second, $microseconds); } /** * Set the instance's timestamp. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $unixTimestamp - * - * @return static */ - #[ReturnTypeWillChange] - public function setTimestamp($unixTimestamp) + public function setTimestamp(float|int|string $timestamp): static { - [$timestamp, $microseconds] = self::getIntegerAndDecimalParts($unixTimestamp); + [$seconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp); - return parent::setTimestamp((int) $timestamp)->setMicroseconds((int) $microseconds); + return parent::setTimestamp((int) $seconds)->setMicroseconds((int) $microseconds); } /** * Set the time by time string. - * - * @param string $time - * - * @return static */ - public function setTimeFromTimeString($time) + public function setTimeFromTimeString(string $time): static { if (!str_contains($time, ':')) { $time .= ':0'; @@ -1592,12 +1858,8 @@ trait Date /** * @alias setTimezone - * - * @param DateTimeZone|string $value - * - * @return static */ - public function timezone($value) + public function timezone(DateTimeZone|string|int $value): static { return $this->setTimezone($value); } @@ -1605,13 +1867,11 @@ trait Date /** * Set the timezone or returns the timezone name if no arguments passed. * - * @param DateTimeZone|string $value - * - * @return static|string + * @return ($value is null ? string : static) */ - public function tz($value = null) + public function tz(DateTimeZone|string|int|null $value = null): static|string { - if (\func_num_args() < 1) { + if ($value === null) { return $this->tzName; } @@ -1620,31 +1880,16 @@ trait Date /** * Set the instance's timezone from a string or object. - * - * @param DateTimeZone|string $value - * - * @return static */ - #[ReturnTypeWillChange] - public function setTimezone($value) + public function setTimezone(DateTimeZone|string|int $timeZone): static { - $tz = static::safeCreateDateTimeZone($value); - - if ($tz === false && !self::isStrictModeEnabled()) { - $tz = new CarbonTimeZone(); - } - - return parent::setTimezone($tz); + return parent::setTimezone(static::safeCreateDateTimeZone($timeZone)); } /** * Set the instance's timezone from a string or object and add/subtract the offset difference. - * - * @param DateTimeZone|string $value - * - * @return static */ - public function shiftTimezone($value) + public function shiftTimezone(DateTimeZone|string $value): static { $dateTimeString = $this->format('Y-m-d H:i:s.u'); @@ -1655,22 +1900,16 @@ trait Date /** * Set the instance's timezone to UTC. - * - * @return static */ - public function utc() + public function utc(): static { return $this->setTimezone('UTC'); } /** * Set the year, month, and date for this instance to that of the passed instance. - * - * @param Carbon|DateTimeInterface $date now if null - * - * @return static */ - public function setDateFrom($date = null) + public function setDateFrom(DateTimeInterface|string $date): static { $date = $this->resolveCarbon($date); @@ -1679,12 +1918,8 @@ trait Date /** * Set the hour, minute, second and microseconds for this instance to that of the passed instance. - * - * @param Carbon|DateTimeInterface $date now if null - * - * @return static */ - public function setTimeFrom($date = null) + public function setTimeFrom(DateTimeInterface|string $date): static { $date = $this->resolveCarbon($date); @@ -1693,12 +1928,8 @@ trait Date /** * Set the date and time for this instance to that of the passed instance. - * - * @param Carbon|DateTimeInterface $date - * - * @return static */ - public function setDateTimeFrom($date = null) + public function setDateTimeFrom(DateTimeInterface|string $date): static { $date = $this->resolveCarbon($date); @@ -1706,11 +1937,9 @@ trait Date } /** - * Get the days of the week - * - * @return array + * Get the days of the week. */ - public static function getDays() + public static function getDays(): array { return static::$days; } @@ -1719,85 +1948,37 @@ trait Date /////////////////////// WEEK SPECIAL DAYS ///////////////////////// /////////////////////////////////////////////////////////////////// - private static function getFirstDayOfWeek(): int + /** + * Get the first day of week. + * + * @return int + */ + public static function getWeekStartsAt(?string $locale = null): int { return (int) static::getTranslationMessageWith( - static::getTranslator(), - 'first_day_of_week' + $locale ? Translator::get($locale) : static::getTranslator(), + 'first_day_of_week', ); } /** - * Get the first day of week + * Get the last day of week. + * + * @param string $locale local to consider the last day of week. * * @return int */ - public static function getWeekStartsAt() + public static function getWeekEndsAt(?string $locale = null): int { - if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { - return self::getFirstDayOfWeek(); - } - - return static::$weekStartsAt; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekEndsAt optional parameter instead when using endOfWeek method. You can also use the - * 'first_day_of_week' locale setting to change the start of week according to current locale - * selected and implicitly the end of week. - * - * Set the first day of week - * - * @param int|string $day week start day (or 'auto' to get the first day of week from Carbon::getLocale() culture). - * - * @return void - */ - public static function setWeekStartsAt($day) - { - static::$weekStartsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); - } - - /** - * Get the last day of week - * - * @return int - */ - public static function getWeekEndsAt() - { - if (static::$weekStartsAt === static::WEEK_DAY_AUTO) { - return (int) (static::DAYS_PER_WEEK - 1 + self::getFirstDayOfWeek()) % static::DAYS_PER_WEEK; - } - - return static::$weekEndsAt; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * Use $weekStartsAt optional parameter instead when using startOfWeek, floorWeek, ceilWeek - * or roundWeek method. You can also use the 'first_day_of_week' locale setting to change the - * start of week according to current locale selected and implicitly the end of week. - * - * Set the last day of week - * - * @param int|string $day week end day (or 'auto' to get the day before the first day of week - * from Carbon::getLocale() culture). - * - * @return void - */ - public static function setWeekEndsAt($day) - { - static::$weekEndsAt = $day === static::WEEK_DAY_AUTO ? $day : max(0, (7 + $day) % 7); + return static::weekRotate(static::getWeekStartsAt($locale), -1); } /** * Get weekend days - * - * @return array */ - public static function getWeekendDays() + public static function getWeekendDays(): array { - return static::$weekendDays; + return FactoryImmutable::getInstance()->getWeekendDays(); } /** @@ -1822,24 +2003,18 @@ trait Date * ``` * * Set weekend days - * - * @param array $days - * - * @return void */ - public static function setWeekendDays($days) + public static function setWeekendDays(array $days): void { - static::$weekendDays = $days; + FactoryImmutable::getDefaultInstance()->setWeekendDays($days); } /** * Determine if a time string will produce a relative date. * - * @param string $time - * * @return bool true if time match a relative date, false if absolute or invalid time string */ - public static function hasRelativeKeywords($time) + public static function hasRelativeKeywords(?string $time): bool { if (!$time || strtotime($time) === false) { return false; @@ -1857,68 +2032,20 @@ trait Date /////////////////////// STRING FORMATTING ///////////////////////// /////////////////////////////////////////////////////////////////// - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use UTF-8 language packages on every machine. - * - * Set if UTF8 will be used for localized date/time. - * - * @param bool $utf8 - */ - public static function setUtf8($utf8) - { - static::$utf8 = $utf8; - } - - /** - * Format the instance with the current locale. You can set the current - * locale using setlocale() https://php.net/setlocale. - * - * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. - * Use ->isoFormat() instead. - * Deprecated since 2.55.0 - * - * @param string $format - * - * @return string - */ - public function formatLocalized($format) - { - // Check for Windows to find and replace the %e modifier correctly. - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $format = preg_replace('#(?toDateTimeString()); - $formatted = ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) - ? strftime($format, $time) - : @strftime($format, $time); - - return static::$utf8 - ? ( - \function_exists('mb_convert_encoding') - ? mb_convert_encoding($formatted, 'UTF-8', mb_list_encodings()) - : utf8_encode($formatted) - ) - : $formatted; - } - /** * Returns list of locale formats for ISO formatting. * * @param string|null $locale current locale used if null - * - * @return array */ - public function getIsoFormats($locale = null) + public function getIsoFormats(?string $locale = null): array { return [ - 'LT' => $this->getTranslationMessage('formats.LT', $locale, 'h:mm A'), - 'LTS' => $this->getTranslationMessage('formats.LTS', $locale, 'h:mm:ss A'), - 'L' => $this->getTranslationMessage('formats.L', $locale, 'MM/DD/YYYY'), - 'LL' => $this->getTranslationMessage('formats.LL', $locale, 'MMMM D, YYYY'), - 'LLL' => $this->getTranslationMessage('formats.LLL', $locale, 'MMMM D, YYYY h:mm A'), - 'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'), + 'LT' => $this->getTranslationMessage('formats.LT', $locale), + 'LTS' => $this->getTranslationMessage('formats.LTS', $locale), + 'L' => $this->getTranslationMessage('formats.L', $locale), + 'LL' => $this->getTranslationMessage('formats.LL', $locale), + 'LLL' => $this->getTranslationMessage('formats.LLL', $locale), + 'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale), 'l' => $this->getTranslationMessage('formats.l', $locale), 'll' => $this->getTranslationMessage('formats.ll', $locale), 'lll' => $this->getTranslationMessage('formats.lll', $locale), @@ -1930,10 +2057,8 @@ trait Date * Returns list of calendar formats for ISO formatting. * * @param string|null $locale current locale used if null - * - * @return array */ - public function getCalendarFormats($locale = null) + public function getCalendarFormats(?string $locale = null): array { return [ 'sameDay' => $this->getTranslationMessage('calendar.sameDay', $locale, '[Today at] LT'), @@ -1947,129 +2072,122 @@ trait Date /** * Returns list of locale units for ISO formatting. - * - * @return array */ - public static function getIsoUnits() + public static function getIsoUnits(): array { static $units = null; - if ($units === null) { - $units = [ - 'OD' => ['getAltNumber', ['day']], - 'OM' => ['getAltNumber', ['month']], - 'OY' => ['getAltNumber', ['year']], - 'OH' => ['getAltNumber', ['hour']], - 'Oh' => ['getAltNumber', ['h']], - 'Om' => ['getAltNumber', ['minute']], - 'Os' => ['getAltNumber', ['second']], - 'D' => 'day', - 'DD' => ['rawFormat', ['d']], - 'Do' => ['ordinal', ['day', 'D']], - 'd' => 'dayOfWeek', - 'dd' => function (CarbonInterface $date, $originalFormat = null) { - return $date->getTranslatedMinDayName($originalFormat); - }, - 'ddd' => function (CarbonInterface $date, $originalFormat = null) { - return $date->getTranslatedShortDayName($originalFormat); - }, - 'dddd' => function (CarbonInterface $date, $originalFormat = null) { - return $date->getTranslatedDayName($originalFormat); - }, - 'DDD' => 'dayOfYear', - 'DDDD' => ['getPaddedUnit', ['dayOfYear', 3]], - 'DDDo' => ['ordinal', ['dayOfYear', 'DDD']], - 'e' => ['weekday', []], - 'E' => 'dayOfWeekIso', - 'H' => ['rawFormat', ['G']], - 'HH' => ['rawFormat', ['H']], - 'h' => ['rawFormat', ['g']], - 'hh' => ['rawFormat', ['h']], - 'k' => 'noZeroHour', - 'kk' => ['getPaddedUnit', ['noZeroHour']], - 'hmm' => ['rawFormat', ['gi']], - 'hmmss' => ['rawFormat', ['gis']], - 'Hmm' => ['rawFormat', ['Gi']], - 'Hmmss' => ['rawFormat', ['Gis']], - 'm' => 'minute', - 'mm' => ['rawFormat', ['i']], - 'a' => 'meridiem', - 'A' => 'upperMeridiem', - 's' => 'second', - 'ss' => ['getPaddedUnit', ['second']], - 'S' => function (CarbonInterface $date) { - return (string) floor($date->micro / 100000); - }, - 'SS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro / 10000), 2, '0', STR_PAD_LEFT); - }, - 'SSS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro / 1000), 3, '0', STR_PAD_LEFT); - }, - 'SSSS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro / 100), 4, '0', STR_PAD_LEFT); - }, - 'SSSSS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro / 10), 5, '0', STR_PAD_LEFT); - }, - 'SSSSSS' => ['getPaddedUnit', ['micro', 6]], - 'SSSSSSS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro * 10), 7, '0', STR_PAD_LEFT); - }, - 'SSSSSSSS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro * 100), 8, '0', STR_PAD_LEFT); - }, - 'SSSSSSSSS' => function (CarbonInterface $date) { - return str_pad((string) floor($date->micro * 1000), 9, '0', STR_PAD_LEFT); - }, - 'M' => 'month', - 'MM' => ['rawFormat', ['m']], - 'MMM' => function (CarbonInterface $date, $originalFormat = null) { - $month = $date->getTranslatedShortMonthName($originalFormat); - $suffix = $date->getTranslationMessage('mmm_suffix'); - if ($suffix && $month !== $date->monthName) { - $month .= $suffix; - } + $units ??= [ + 'OD' => ['getAltNumber', ['day']], + 'OM' => ['getAltNumber', ['month']], + 'OY' => ['getAltNumber', ['year']], + 'OH' => ['getAltNumber', ['hour']], + 'Oh' => ['getAltNumber', ['h']], + 'Om' => ['getAltNumber', ['minute']], + 'Os' => ['getAltNumber', ['second']], + 'D' => 'day', + 'DD' => ['rawFormat', ['d']], + 'Do' => ['ordinal', ['day', 'D']], + 'd' => 'dayOfWeek', + 'dd' => static fn (CarbonInterface $date, $originalFormat = null) => $date->getTranslatedMinDayName( + $originalFormat, + ), + 'ddd' => static fn (CarbonInterface $date, $originalFormat = null) => $date->getTranslatedShortDayName( + $originalFormat, + ), + 'dddd' => static fn (CarbonInterface $date, $originalFormat = null) => $date->getTranslatedDayName( + $originalFormat, + ), + 'DDD' => 'dayOfYear', + 'DDDD' => ['getPaddedUnit', ['dayOfYear', 3]], + 'DDDo' => ['ordinal', ['dayOfYear', 'DDD']], + 'e' => ['weekday', []], + 'E' => 'dayOfWeekIso', + 'H' => ['rawFormat', ['G']], + 'HH' => ['rawFormat', ['H']], + 'h' => ['rawFormat', ['g']], + 'hh' => ['rawFormat', ['h']], + 'k' => 'noZeroHour', + 'kk' => ['getPaddedUnit', ['noZeroHour']], + 'hmm' => ['rawFormat', ['gi']], + 'hmmss' => ['rawFormat', ['gis']], + 'Hmm' => ['rawFormat', ['Gi']], + 'Hmmss' => ['rawFormat', ['Gis']], + 'm' => 'minute', + 'mm' => ['rawFormat', ['i']], + 'a' => 'meridiem', + 'A' => 'upperMeridiem', + 's' => 'second', + 'ss' => ['getPaddedUnit', ['second']], + 'S' => static fn (CarbonInterface $date) => (string) floor($date->micro / 100000), + 'SS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro / 10000, 2), + 'SSS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro / 1000, 3), + 'SSSS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro / 100, 4), + 'SSSSS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro / 10, 5), + 'SSSSSS' => ['getPaddedUnit', ['micro', 6]], + 'SSSSSSS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro * 10, 7), + 'SSSSSSSS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro * 100, 8), + 'SSSSSSSSS' => static fn (CarbonInterface $date) => self::floorZeroPad($date->micro * 1000, 9), + 'M' => 'month', + 'MM' => ['rawFormat', ['m']], + 'MMM' => static fn (CarbonInterface $date, $originalFormat = null) => $date->getTranslatedShortMonthName( + $originalFormat, + ), + 'MMMM' => static fn (CarbonInterface $date, $originalFormat = null) => $date->getTranslatedMonthName( + $originalFormat, + ), + 'Mo' => ['ordinal', ['month', 'M']], + 'Q' => 'quarter', + 'Qo' => ['ordinal', ['quarter', 'M']], + 'G' => 'isoWeekYear', + 'GG' => ['getPaddedUnit', ['isoWeekYear']], + 'GGG' => ['getPaddedUnit', ['isoWeekYear', 3]], + 'GGGG' => ['getPaddedUnit', ['isoWeekYear', 4]], + 'GGGGG' => ['getPaddedUnit', ['isoWeekYear', 5]], + 'g' => 'weekYear', + 'gg' => ['getPaddedUnit', ['weekYear']], + 'ggg' => ['getPaddedUnit', ['weekYear', 3]], + 'gggg' => ['getPaddedUnit', ['weekYear', 4]], + 'ggggg' => ['getPaddedUnit', ['weekYear', 5]], + 'W' => 'isoWeek', + 'WW' => ['getPaddedUnit', ['isoWeek']], + 'Wo' => ['ordinal', ['isoWeek', 'W']], + 'w' => 'week', + 'ww' => ['getPaddedUnit', ['week']], + 'wo' => ['ordinal', ['week', 'w']], + 'x' => ['valueOf', []], + 'X' => 'timestamp', + 'Y' => 'year', + 'YY' => ['rawFormat', ['y']], + 'YYYY' => ['getPaddedUnit', ['year', 4]], + 'YYYYY' => ['getPaddedUnit', ['year', 5]], + 'YYYYYY' => static fn (CarbonInterface $date) => ($date->year < 0 ? '' : '+'). + $date->getPaddedUnit('year', 6), + 'N' => ['eraAbbr', []], + 'NN' => ['eraAbbr', []], + 'NNN' => ['eraAbbr', []], + 'NNNN' => ['eraName', []], + 'NNNNN' => ['eraNarrow', []], + 'y' => static fn (CarbonInterface $date) => (string) $date->eraYear(), + 'yy' => static fn (CarbonInterface $date) => self::zeroPadSigned($date->eraYear(), 2), + 'yyy' => static fn (CarbonInterface $date) => self::zeroPadSigned($date->eraYear(), 3), + 'yyyy' => static fn (CarbonInterface $date) => self::zeroPadSigned($date->eraYear(), 4), + 'yo' => static function (CarbonInterface $date) { + $year = $date->eraYear(); - return $month; - }, - 'MMMM' => function (CarbonInterface $date, $originalFormat = null) { - return $date->getTranslatedMonthName($originalFormat); - }, - 'Mo' => ['ordinal', ['month', 'M']], - 'Q' => 'quarter', - 'Qo' => ['ordinal', ['quarter', 'M']], - 'G' => 'isoWeekYear', - 'GG' => ['getPaddedUnit', ['isoWeekYear']], - 'GGG' => ['getPaddedUnit', ['isoWeekYear', 3]], - 'GGGG' => ['getPaddedUnit', ['isoWeekYear', 4]], - 'GGGGG' => ['getPaddedUnit', ['isoWeekYear', 5]], - 'g' => 'weekYear', - 'gg' => ['getPaddedUnit', ['weekYear']], - 'ggg' => ['getPaddedUnit', ['weekYear', 3]], - 'gggg' => ['getPaddedUnit', ['weekYear', 4]], - 'ggggg' => ['getPaddedUnit', ['weekYear', 5]], - 'W' => 'isoWeek', - 'WW' => ['getPaddedUnit', ['isoWeek']], - 'Wo' => ['ordinal', ['isoWeek', 'W']], - 'w' => 'week', - 'ww' => ['getPaddedUnit', ['week']], - 'wo' => ['ordinal', ['week', 'w']], - 'x' => ['valueOf', []], - 'X' => 'timestamp', - 'Y' => 'year', - 'YY' => ['rawFormat', ['y']], - 'YYYY' => ['getPaddedUnit', ['year', 4]], - 'YYYYY' => ['getPaddedUnit', ['year', 5]], - 'YYYYYY' => function (CarbonInterface $date) { - return ($date->year < 0 ? '' : '+').$date->getPaddedUnit('year', 6); - }, - 'z' => ['rawFormat', ['T']], - 'zz' => 'tzName', - 'Z' => ['getOffsetString', []], - 'ZZ' => ['getOffsetString', ['']], - ]; - } + try { + $result = $date->translate('ordinal', [':number' => $year, ':period' => 'y']); + + return $result === 'ordinal' ? (string) $year : $result; + } catch (InvalidTypeException) { + return (string) $year; + } + }, + 'z' => ['rawFormat', ['T']], + 'zz' => 'tzName', + 'Z' => ['getOffsetString', []], + 'ZZ' => ['getOffsetString', ['']], + ]; return $units; } @@ -2081,21 +2199,14 @@ trait Date * @param int $length Length of the output (2 by default) * @param string $padString String to use for padding ("0" by default) * @param int $padType Side(s) to pad (STR_PAD_LEFT by default) - * - * @return string */ - public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = STR_PAD_LEFT) + public function getPaddedUnit($unit, $length = 2, $padString = '0', $padType = STR_PAD_LEFT): string { return ($this->$unit < 0 ? '-' : '').str_pad((string) abs($this->$unit), $length, $padString, $padType); } /** * Return a property with its ordinal. - * - * @param string $key - * @param string|null $period - * - * @return string */ public function ordinal(string $key, ?string $period = null): string { @@ -2108,17 +2219,67 @@ trait Date return (string) ($result === 'ordinal' ? $number : $result); } + /** + * Return the abbreviated name of the era the current date is in, according to the "eras" of the current locale + * (empty string if the date is in none of them). + */ + public function eraAbbr(): string + { + return (string) ($this->getEra()['abbr'] ?? ''); + } + + /** + * Return the full name of the era the current date is in, according to the "eras" of the current locale + * (empty string if the date is in none of them). + */ + public function eraName(): string + { + return (string) ($this->getEra()['name'] ?? ''); + } + + /** + * Return the narrow name of the era the current date is in, according to the "eras" of the current locale + * (empty string if the date is in none of them). + */ + public function eraNarrow(): string + { + return (string) ($this->getEra()['narrow'] ?? ''); + } + + /** + * Return the year of the era the current date is in, according to the "eras" of the current locale + * (the calendar year if the date is in none of them). + */ + public function eraYear(): int + { + $era = $this->getEra(); + + if ($era === null) { + return $this->year; + } + + return ($this->year - $era['sinceYear']) * $era['direction'] + $era['offset']; + } + /** * Return the meridiem of the current time in the current locale. * * @param bool $isLower if true, returns lowercase variant if available in the current locale. - * - * @return string */ public function meridiem(bool $isLower = false): string { $hour = $this->hour; - $index = $hour < 12 ? 0 : 1; + $index = $hour < static::HOURS_PER_DAY / 2 ? 0 : 1; + + // A locale that computes its meridiem (zh_CN, bn...) has no meridiem.0/meridiem.1 of its own, + // so the indexed lookup below would fall through to the fallback locale (e.g. en "PM"). + if ($this->getTranslationMessage('meridiem') instanceof Closure) { + return $this->translate('meridiem', [ + ':hour' => $this->hour, + ':minute' => $this->minute, + ':isLower' => $isLower, + ]); + } if ($isLower) { $key = 'meridiem.'.($index + 2); @@ -2152,30 +2313,27 @@ trait Date * Returns the alternative number for a given date property if available in the current locale. * * @param string $key date property - * - * @return string */ public function getAltNumber(string $key): string { - return $this->translateNumber(\strlen($key) > 1 ? $this->$key : $this->rawFormat('h')); + return $this->translateNumber((int) (\strlen($key) > 1 ? $this->$key : $this->rawFormat($key))); } /** * Format in the current language using ISO replacement patterns. * - * @param string $format * @param string|null $originalFormat provide context if a chunk has been passed alone - * - * @return string */ public function isoFormat(string $format, ?string $originalFormat = null): string { $result = ''; $length = mb_strlen($format); - $originalFormat = $originalFormat ?: $format; + $originalFormat ??= $format; $inEscaped = false; $formats = null; $units = null; + $macroIndex = -1; + $macroDepth = 0; for ($i = 0; $i < $length; $i++) { $char = mb_substr($format, $i, 1); @@ -2206,7 +2364,10 @@ trait Date $input = mb_substr($format, $i); - if (preg_match('/^(LTS|LT|l{1,4}|L{1,4})/', $input, $match)) { + // Like moment.js, allow up to 5 nested macro expansions at the same position + $macroDepth = $i === $macroIndex ? $macroDepth + 1 : 0; + + if ($macroDepth < 5 && preg_match('/^(LTS|LT|l{1,4}|L{1,4})/', $input, $match)) { if ($formats === null) { $formats = $this->getIsoFormats(); } @@ -2214,15 +2375,17 @@ trait Date $code = $match[0]; $sequence = $formats[$code] ?? preg_replace_callback( '/MMMM|MM|DD|dddd/', - function ($code) { - return mb_substr($code[0], 1); - }, - $formats[strtoupper($code)] ?? '' + static fn ($code) => mb_substr($code[0], 1), + $formats[strtoupper($code)] ?? '', ); - $rest = mb_substr($format, $i + mb_strlen($code)); - $format = mb_substr($format, 0, $i).$sequence.$rest; + $format = mb_substr($format, 0, $i).$sequence.mb_substr($format, $i + mb_strlen($code)); $length = mb_strlen($format); - $input = $sequence.$rest; + $macroIndex = $i; + // Read the expanded sequence again from its first character, + // it may start with literal text, an escape or another macro + $i--; + + continue; } if (preg_match('/^'.CarbonInterface::ISO_FORMAT_REGEXP.'/', $input, $match)) { @@ -2239,7 +2402,7 @@ trait Date } elseif (\is_array($sequence)) { try { $sequence = $this->{$sequence[0]}(...$sequence[1]); - } catch (ReflectionException | InvalidArgumentException | BadMethodCallException $e) { + } catch (ReflectionException | InvalidArgumentException | BadMethodCallException) { $sequence = ''; } } elseif (\is_string($sequence)) { @@ -2260,59 +2423,55 @@ trait Date /** * List of replacements from date() format to isoFormat(). - * - * @return array */ - public static function getFormatsToIsoReplacements() + public static function getFormatsToIsoReplacements(): array { static $replacements = null; - if ($replacements === null) { - $replacements = [ - 'd' => true, - 'D' => 'ddd', - 'j' => true, - 'l' => 'dddd', - 'N' => true, - 'S' => function ($date) { - $day = $date->rawFormat('j'); - - return str_replace((string) $day, '', $date->isoFormat('Do')); - }, - 'w' => true, - 'z' => true, - 'W' => true, - 'F' => 'MMMM', - 'm' => true, - 'M' => 'MMM', - 'n' => true, - 't' => true, - 'L' => true, - 'o' => true, - 'Y' => true, - 'y' => true, - 'a' => 'a', - 'A' => 'A', - 'B' => true, - 'g' => true, - 'G' => true, - 'h' => true, - 'H' => true, - 'i' => true, - 's' => true, - 'u' => true, - 'v' => true, - 'E' => true, - 'I' => true, - 'O' => true, - 'P' => true, - 'Z' => true, - 'c' => true, - 'r' => true, - 'U' => true, - 'T' => true, - ]; - } + $replacements ??= [ + 'd' => true, + 'D' => 'ddd', + 'j' => true, + 'l' => 'dddd', + 'N' => true, + 'S' => static fn ($date) => str_replace((string) $date->rawFormat('j'), '', $date->isoFormat('Do')), + 'w' => true, + 'z' => true, + 'W' => true, + 'F' => 'MMMM', + 'm' => true, + 'M' => 'MMM', + 'n' => true, + 't' => true, + 'L' => true, + 'o' => true, + 'X' => true, + 'x' => true, + 'Y' => true, + 'y' => true, + 'a' => 'a', + 'A' => 'A', + 'B' => true, + 'g' => true, + 'G' => true, + 'h' => true, + 'H' => true, + 'i' => true, + 's' => true, + 'u' => true, + 'v' => true, + 'E' => true, + 'e' => true, + 'I' => true, + 'O' => true, + 'P' => true, + 'p' => true, + 'Z' => true, + 'c' => true, + 'r' => true, + 'U' => true, + 'T' => true, + ]; return $replacements; } @@ -2320,10 +2479,6 @@ trait Date /** * Format as ->format() do (using date replacements patterns from https://php.net/manual/en/function.date.php) * but translate words whenever possible (months, day names, etc.) using the current locale. - * - * @param string $format - * - * @return string */ public function translatedFormat(string $format): string { @@ -2405,33 +2560,18 @@ trait Date * like "-12:00". * * @param string $separator string to place between hours and minutes (":" by default) - * - * @return string */ - public function getOffsetString($separator = ':') + public function getOffsetString(string $separator = ':'): string { $second = $this->getOffset(); $symbol = $second < 0 ? '-' : '+'; $minute = abs($second) / static::SECONDS_PER_MINUTE; - $hour = str_pad((string) floor($minute / static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); - $minute = str_pad((string) (((int) $minute) % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT); + $hour = self::floorZeroPad($minute / static::MINUTES_PER_HOUR, 2); + $minute = self::floorZeroPad(((int) $minute) % static::MINUTES_PER_HOUR, 2); return "$symbol$hour$separator$minute"; } - protected static function executeStaticCallable($macro, ...$parameters) - { - return static::bindMacroContext(null, function () use (&$macro, &$parameters) { - if ($macro instanceof Closure) { - $boundMacro = @Closure::bind($macro, null, static::class); - - return ($boundMacro ?: $macro)(...$parameters); - } - - return $macro(...$parameters); - }); - } - /** * Dynamically handle calls to the class. * @@ -2439,48 +2579,95 @@ trait Date * @param array $parameters parameters list * * @throws BadMethodCallException - * - * @return mixed */ - public static function __callStatic($method, $parameters) + public static function __callStatic(string $method, array $parameters): mixed { if (!static::hasMacro($method)) { foreach (static::getGenericMacros() as $callback) { try { return static::executeStaticCallable($callback, $method, ...$parameters); - } catch (BadMethodCallException $exception) { + } catch (BadMethodCallException) { continue; } } + if (static::isStrictModeEnabled()) { - throw new UnknownMethodException(sprintf('%s::%s', static::class, $method)); + throw new UnknownMethodException(\sprintf('%s::%s', static::class, $method)); } return null; } - return static::executeStaticCallable(static::$globalMacros[$method], ...$parameters); + return static::executeStaticCallable(static::getMacro($method), ...$parameters); } /** * Set specified unit to new given value. * - * @param string $unit year, month, day, hour, minute, second or microsecond - * @param int $value new value for given unit - * - * @return static + * @param string $unit year, month, day, hour, minute, second or microsecond + * @param Month|int|float|null $value new value for given unit + * @param OverflowMode|bool|null $overflow either overflow (when the day does not exist in current month) + * is allowed or not, or should use an anchor day + * @param ?int $anchorDay the day to which to go after setting year/quarter/month, + * if given but it does not exist in current month, it will + * go to the last day of the month */ - public function setUnit($unit, $value = null) - { + public function setUnit( + string $unit, + Month|int|float|null $value = null, + OverflowMode|bool|null $overflow = null, + ?int $anchorDay = null, + ): static { + if (\is_float($value)) { + $int = (int) $value; + + if ((float) $int !== $value) { + throw new InvalidArgumentException( + "$unit cannot be changed to float value $value, integer expected", + ); + } + + $value = $int; + } + + $overflow = $this->getOverflowMode($overflow, $anchorDay); $unit = static::singularUnit($unit); + $value = self::monthToInt($value, $unit); $dateUnits = ['year', 'month', 'day']; + if (\in_array($unit, $dateUnits)) { - return $this->setDate(...array_map(function ($name) use ($unit, $value) { - return (int) ($name === $unit ? $value : $this->$name); - }, $dateUnits)); + if ($unit !== 'day') { + $overflow ??= $this->shouldUnitOverflow($unit) + ? OverflowMode::Overflow + : OverflowMode::NoOverflow; + $anchorDay = match ($overflow) { + OverflowMode::AnchorDay => $anchorDay ?? $this->day, + OverflowMode::NoOverflow => $this->day, + default => null, + }; + + if ($anchorDay !== null) { + $date = $this->setDate(...array_map( + fn ($name) => (int) (match ($name) { + $unit => $value, + 'day' => 1, + default => $this->$name, + }), + $dateUnits, + )); + + return $date->day(min($anchorDay, $date->daysInMonth)); + } + } + + return $this->setDate(...array_map( + fn ($name) => (int) ($name === $unit ? $value : $this->$name), + $dateUnits, + )); } $units = ['hour', 'minute', 'second', 'micro']; + if ($unit === 'millisecond' || $unit === 'milli') { $value *= 1000; $unit = 'micro'; @@ -2488,80 +2675,49 @@ trait Date $unit = 'micro'; } - return $this->setTime(...array_map(function ($name) use ($unit, $value) { - return (int) ($name === $unit ? $value : $this->$name); - }, $units)); + return $this->setTime(...array_map( + fn ($name) => (int) ($name === $unit ? $value : $this->$name), + $units, + )); } /** * Returns standardized singular of a given singular/plural unit name (in English). - * - * @param string $unit - * - * @return string */ public static function singularUnit(string $unit): string { $unit = rtrim(mb_strtolower($unit), 's'); - if ($unit === 'centurie') { - return 'century'; - } - - if ($unit === 'millennia') { - return 'millennium'; - } - - return $unit; + return match ($unit) { + 'centurie' => 'century', + 'millennia' => 'millennium', + default => $unit, + }; } /** * Returns standardized plural of a given singular/plural unit name (in English). - * - * @param string $unit - * - * @return string */ public static function pluralUnit(string $unit): string { $unit = rtrim(strtolower($unit), 's'); - if ($unit === 'century') { - return 'centuries'; - } - - if ($unit === 'millennium' || $unit === 'millennia') { - return 'millennia'; - } - - return "{$unit}s"; + return match ($unit) { + 'century' => 'centuries', + 'millennium', 'millennia' => 'millennia', + default => "{$unit}s", + }; } - protected function executeCallable($macro, ...$parameters) + public static function sleep(int|float $seconds): void { - if ($macro instanceof Closure) { - $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + if (static::hasTestNow()) { + static::setTestNow(static::getTestNow()->avoidMutation()->addSeconds($seconds)); - return ($boundMacro ?: $macro)(...$parameters); + return; } - return $macro(...$parameters); - } - - protected function executeCallableWithContext($macro, ...$parameters) - { - return static::bindMacroContext($this, function () use (&$macro, &$parameters) { - return $this->executeCallable($macro, ...$parameters); - }); - } - - protected static function getGenericMacros() - { - foreach (static::$globalGenericMacros as $list) { - foreach ($list as $macro) { - yield $macro; - } - } + (new NativeClock('UTC'))->sleep($seconds); } /** @@ -2571,10 +2727,193 @@ trait Date * @param array $parameters parameters list * * @throws UnknownMethodException|BadMethodCallException|ReflectionException|Throwable - * - * @return mixed */ - public function __call($method, $parameters) + public function __call(string $method, array $parameters): mixed + { + $unit = rtrim($method, 's'); + + return $this->callPlusOrMinus($method, $parameters) + ?? $this->callDiffAlias($unit, $parameters) + ?? $this->callHumanDiffAlias($unit, $parameters) + ?? $this->callRoundMethod($unit, $parameters) + ?? $this->callIsMethod($unit, $parameters) + ?? $this->callModifierMethod($unit, $parameters) + ?? $this->callPeriodMethod($method, $parameters) + ?? $this->callGetOrSetMethod($method, $parameters) + ?? $this->callMacroMethod($method, $parameters); + } + + /** + * Return the Carbon instance passed through, a now instance in the same timezone + * if null given or parse the input if string given. + */ + protected function resolveCarbon(DateTimeInterface|string|null $date): self + { + if (!$date) { + return $this->nowWithSameTz(); + } + + if (\is_string($date)) { + return $this->transmitFactory(fn () => static::parse($date, $this->getTimezone())); + } + + return $date instanceof self ? $date : $this->transmitFactory(static fn () => static::instance($date)); + } + + protected static function weekRotate(int $day, int $rotation): int + { + return (static::DAYS_PER_WEEK + $rotation % static::DAYS_PER_WEEK + $day) % static::DAYS_PER_WEEK; + } + + protected function executeCallable(callable $macro, ...$parameters) + { + if ($macro instanceof Closure) { + $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class); + + return \call_user_func_array($boundMacro ?: $macro, $parameters); + } + + return \call_user_func_array($macro, $parameters); + } + + protected function executeCallableWithContext(callable $macro, ...$parameters) + { + return static::bindMacroContext($this, function () use (&$macro, &$parameters) { + return $this->executeCallable($macro, ...$parameters); + }); + } + + protected function getAllGenericMacros(): Generator + { + yield from $this->localGenericMacros ?? []; + yield from $this->transmitFactory(static fn () => static::getGenericMacros()); + } + + protected static function getGenericMacros(): Generator + { + foreach ((FactoryImmutable::getInstance()->getSettings()['genericMacros'] ?? []) as $list) { + foreach ($list as $macro) { + yield $macro; + } + } + } + + protected static function executeStaticCallable(callable $macro, ...$parameters) + { + return static::bindMacroContext(null, function () use (&$macro, &$parameters) { + if ($macro instanceof Closure) { + $boundMacro = @Closure::bind($macro, null, static::class); + + return \call_user_func_array($boundMacro ?: $macro, $parameters); + } + + return \call_user_func_array($macro, $parameters); + }); + } + + protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue) + { + $key = $baseKey.$keySuffix; + $standaloneKey = $key.'_standalone'; + $baseTranslation = $this->getTranslationMessage($key); + + if ($baseTranslation instanceof Closure) { + return $baseTranslation($this, $context, $subKey) ?: $defaultValue; + } + + if ( + $this->getTranslationMessage("$standaloneKey.$subKey") && + (!$context || (($regExp = $this->getTranslationMessage($baseKey.'_regexp')) && !preg_match($regExp, $context))) + ) { + $key = $standaloneKey; + } + + return $this->getTranslationMessage("$key.$subKey", null, $defaultValue); + } + + private function callGetOrSetMethod(string $method, array $parameters): mixed + { + if (preg_match('/^([a-z]{2,})(In|Of)([A-Z][a-z]+)$/', $method)) { + $localStrictModeEnabled = $this->localStrictModeEnabled; + $this->localStrictModeEnabled = true; + + try { + return $this->callGetOrSet($method, $parameters[0] ?? null); + } catch (UnknownGetterException|UnknownSetterException|ImmutableException) { + // continue to macro + } finally { + $this->localStrictModeEnabled = $localStrictModeEnabled; + } + } + + return null; + } + + private function callGetOrSet(string $name, mixed $value): mixed + { + if ($value !== null) { + if (\is_string($value) || \is_int($value) || \is_float($value) || $value instanceof DateTimeZone || $value instanceof Month) { + return $this->set($name, $value); + } + + return null; + } + + return $this->get($name); + } + + private function getUTCUnit(string $unit): ?string + { + if (str_starts_with($unit, 'Real')) { + return substr($unit, 4); + } + + if (str_starts_with($unit, 'UTC')) { + return substr($unit, 3); + } + + return null; + } + + private function callDiffAlias(string $method, array $parameters): mixed + { + if (preg_match('/^(diff|floatDiff)In(Real|UTC|Utc)?(.+)$/', $method, $match)) { + $mode = strtoupper($match[2] ?? ''); + $betterMethod = $match[1] === 'floatDiff' ? str_replace('floatDiff', 'diff', $method) : null; + + if ($mode === 'REAL') { + $mode = 'UTC'; + $betterMethod = str_replace($match[2], 'UTC', $betterMethod ?? $method); + } + + if ($betterMethod) { + @trigger_error( + "Use the method $betterMethod instead to make it more explicit about what it does.\n". + 'On next major version, "float" prefix will be removed (as all diff are now returning floating numbers)'. + ' and "Real" methods will be removed in favor of "UTC" because what it actually does is to convert both'. + ' dates to UTC timezone before comparison, while by default it does it only if both dates don\'t have'. + ' exactly the same timezone (Note: 2 timezones with the same offset but different names are considered'. + " different as it's not safe to assume they will always have the same offset).", + \E_USER_DEPRECATED, + ); + } + + $unit = self::pluralUnit($match[3]); + $diffMethod = 'diffIn'.ucfirst($unit); + + if (\in_array($unit, ['days', 'weeks', 'months', 'quarters', 'years'])) { + $parameters['utc'] = ($mode === 'UTC'); + } + + if (method_exists($this, $diffMethod)) { + return $this->$diffMethod(...$parameters); + } + } + + return null; + } + + private function callHumanDiffAlias(string $method, array $parameters): ?string { $diffSizes = [ // @mode diffForHumans @@ -2595,7 +2934,7 @@ trait Date $sizePattern = implode('|', array_keys($diffSizes)); $syntaxPattern = implode('|', array_keys($diffSyntaxModes)); - if (preg_match("/^(?$sizePattern)(?$syntaxPattern)DiffForHumans$/", $method, $match)) { + if (preg_match("/^(?$sizePattern)(?$syntaxPattern)DiffForHuman$/", $method, $match)) { $dates = array_filter($parameters, function ($parameter) { return $parameter instanceof DateTimeInterface; }); @@ -2610,38 +2949,73 @@ trait Date return $this->diffForHumans($other, $diffSyntaxModes[$match['syntax']], $diffSizes[$match['size']], ...$parameters); } - $roundedValue = $this->callRoundMethod($method, $parameters); + return null; + } - if ($roundedValue !== null) { - return $roundedValue; + private function callIsMethod(string $unit, array $parameters): ?bool + { + if (!str_starts_with($unit, 'is')) { + return null; } - $unit = rtrim($method, 's'); + $word = substr($unit, 2); - if (str_starts_with($unit, 'is')) { - $word = substr($unit, 2); + if (\in_array($word, static::$days, true)) { + return $this->isDayOfWeek($word); + } - if (\in_array($word, static::$days, true)) { - return $this->isDayOfWeek($word); - } + return match ($word) { + // @call is Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) + 'Utc', 'UTC' => $this->utc, + // @call is Check if the current instance has non-UTC timezone. + 'Local' => $this->local, + // @call is Check if the current instance is a valid date. + 'Valid' => $this->year !== 0, + // @call is Check if the current instance is in a daylight saving time. + 'DST' => $this->dst, + default => $this->callComparatorMethod($word, $parameters), + }; + } - switch ($word) { - // @call is Check if the current instance has UTC timezone. (Both isUtc and isUTC cases are valid.) - case 'Utc': - case 'UTC': - return $this->utc; - // @call is Check if the current instance has non-UTC timezone. - case 'Local': - return $this->local; - // @call is Check if the current instance is a valid date. - case 'Valid': - return $this->year !== 0; - // @call is Check if the current instance is in a daylight saving time. - case 'DST': - return $this->dst; + private function callComparatorMethod(string $unit, array $parameters): ?bool + { + $start = substr($unit, 0, 4); + $factor = -1; + + if ($start === 'Last') { + $start = 'Next'; + $factor = 1; + } + + if ($start === 'Next') { + $lowerUnit = strtolower(substr($unit, 4)); + + if (static::isModifiableUnit($lowerUnit)) { + return $this->avoidMutation()->addUnit($lowerUnit, $factor, false)->isSameUnit($lowerUnit, ...($parameters ?: ['now'])); } } + if ($start === 'Same') { + try { + return $this->isSameUnit(strtolower(substr($unit, 4)), ...$parameters); + } catch (BadComparisonUnitException) { + // Try next + } + } + + if (str_starts_with($unit, 'Current')) { + try { + return $this->isCurrentUnit(strtolower(substr($unit, 7))); + } catch (BadComparisonUnitException | BadMethodCallException) { + // Try next + } + } + + return null; + } + + private function callModifierMethod(string $unit, array $parameters): ?static + { $action = substr($unit, 0, 3); $overflow = null; @@ -2655,11 +3029,12 @@ trait Date if ($action === 'add' || $action === 'sub') { $unit = substr($unit, 3); + $utcUnit = $this->getUTCUnit($unit); - if (str_starts_with($unit, 'Real')) { - $unit = static::singularUnit(substr($unit, 4)); + if ($utcUnit) { + $unit = static::singularUnit($utcUnit); - return $this->{"{$action}RealUnit"}($unit, ...$parameters); + return $this->{"{$action}UTCUnit"}($unit, ...$parameters); } if (preg_match('/^(Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia)s?(No|With|Without|WithNo)Overflow$/', $unit, $match)) { @@ -2671,41 +3046,27 @@ trait Date } if (static::isModifiableUnit($unit)) { - return $this->{"{$action}Unit"}($unit, $this->getMagicParameter($parameters, 0, 'value', 1), $overflow); - } + $anchorDay = $this->getMagicParameter($parameters, 1, 'anchorDay', null); - $sixFirstLetters = substr($unit, 0, 6); - $factor = -1; - - if ($sixFirstLetters === 'isLast') { - $sixFirstLetters = 'isNext'; - $factor = 1; - } - - if ($sixFirstLetters === 'isNext') { - $lowerUnit = strtolower(substr($unit, 6)); - - if (static::isModifiableUnit($lowerUnit)) { - return $this->copy()->addUnit($lowerUnit, $factor, false)->isSameUnit($lowerUnit, ...$parameters); + if ($anchorDay !== null && $overflow) { + throw new InvalidArgumentException( + '$anchorDay is not compatible with overflow', + ); } + + return $this->{"{$action}Unit"}( + $unit, + $this->getMagicParameter($parameters, 0, 'value', 1), + $anchorDay === null ? $overflow : OverflowMode::AnchorDay, + $anchorDay, + ); } - if ($sixFirstLetters === 'isSame') { - try { - return $this->isSameUnit(strtolower(substr($unit, 6)), ...$parameters); - } catch (BadComparisonUnitException $exception) { - // Try next - } - } - - if (str_starts_with($unit, 'isCurrent')) { - try { - return $this->isCurrentUnit(strtolower(substr($unit, 9))); - } catch (BadComparisonUnitException | BadMethodCallException $exception) { - // Try next - } - } + return null; + } + private function callPeriodMethod(string $method, array $parameters): ?CarbonPeriod + { if (str_ends_with($method, 'Until')) { try { $unit = static::singularUnit(substr($method, 0, -5)); @@ -2715,26 +3076,29 @@ trait Date $this->getMagicParameter($parameters, 1, 'factor', 1), $unit ); - } catch (InvalidArgumentException $exception) { + } catch (InvalidArgumentException) { // Try macros } } + return null; + } + + private function callMacroMethod(string $method, array $parameters): mixed + { return static::bindMacroContext($this, function () use (&$method, &$parameters) { $macro = $this->getLocalMacro($method); if (!$macro) { - foreach ([$this->localGenericMacros ?: [], static::getGenericMacros()] as $list) { - foreach ($list as $callback) { - try { - return $this->executeCallable($callback, $method, ...$parameters); - } catch (BadMethodCallException $exception) { - continue; - } + foreach ($this->getAllGenericMacros() as $callback) { + try { + return $this->executeCallable($callback, $method, ...$parameters); + } catch (BadMethodCallException) { + continue; } } - if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + if ($this->isLocalStrictModeEnabled()) { throw new UnknownMethodException($method); } @@ -2744,4 +3108,33 @@ trait Date return $this->executeCallable($macro, ...$parameters); }); } + + private static function floorZeroPad(int|float $value, int $length): string + { + return str_pad((string) floor($value), $length, '0', STR_PAD_LEFT); + } + + private static function zeroPadSigned(int $value, int $length): string + { + return ($value < 0 ? '-' : '').str_pad((string) abs($value), $length, '0', STR_PAD_LEFT); + } + + /** + * @template T of CarbonInterface + * + * @param T $date + * + * @return T + */ + private function mutateIfMutable(CarbonInterface $date): CarbonInterface + { + return $this instanceof DateTimeImmutable + ? $date + : $this->modify('@'.$date->rawFormat('U.u'))->setTimezone($date->getTimezone()); + } + + private function setAnchorDayIfNotNull(?int $anchorDay): static + { + return $anchorDay === null ? $this : $this->setAnchorDay($anchorDay); + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedPeriodProperties.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedPeriodProperties.php new file mode 100644 index 000000000..71660c3a8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedPeriodProperties.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\CarbonInterval; + +trait DeprecatedPeriodProperties +{ + /** + * Period start in PHP < 8.2. + * + * @var CarbonInterface + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period start. + */ + public $start; + + /** + * Period end in PHP < 8.2. + * + * @var CarbonInterface|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period end. + */ + public $end; + + /** + * Period current iterated date in PHP < 8.2. + * + * @var CarbonInterface|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period current iterated date. + */ + public $current; + + /** + * Period interval in PHP < 8.2. + * + * @var CarbonInterval|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period interval. + */ + public $interval; + + /** + * Period recurrences in PHP < 8.2. + * + * @var int|float|null + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period recurrences. + */ + public $recurrences; + + /** + * Period start included option in PHP < 8.2. + * + * @var bool + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period start included option. + */ + public $include_start_date; + + /** + * Period end included option in PHP < 8.2. + * + * @var bool + * + * @deprecated PHP 8.2 this property is no longer in sync with the actual period end included option. + */ + public $include_end_date; +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php deleted file mode 100644 index 5acc6f5c7..000000000 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/DeprecatedProperties.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Carbon\Traits; - -trait DeprecatedProperties -{ - /** - * the day of week in current locale LC_TIME - * - * @var string - * - * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. - * Use ->isoFormat('MMM') instead. - * Deprecated since 2.55.0 - */ - public $localeDayOfWeek; - - /** - * the abbreviated day of week in current locale LC_TIME - * - * @var string - * - * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. - * Use ->isoFormat('dddd') instead. - * Deprecated since 2.55.0 - */ - public $shortLocaleDayOfWeek; - - /** - * the month in current locale LC_TIME - * - * @var string - * - * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. - * Use ->isoFormat('ddd') instead. - * Deprecated since 2.55.0 - */ - public $localeMonth; - - /** - * the abbreviated month in current locale LC_TIME - * - * @var string - * - * @deprecated It uses OS language package and strftime() which is deprecated since PHP 8.1. - * Use ->isoFormat('MMMM') instead. - * Deprecated since 2.55.0 - */ - public $shortLocaleMonth; -} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php index ab5b65d23..7256755fb 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Difference.php @@ -1,5 +1,7 @@ s !== 0 || $diff->i !== 0 || $diff->h !== 0 || $diff->d !== 0 || $diff->m !== 0 || $diff->y !== 0) { - $diff->f = (round($diff->f * 1000000) + 1000000) / 1000000; - $diff->s--; - - if ($diff->s < 0) { - $diff->s += 60; - $diff->i--; - - if ($diff->i < 0) { - $diff->i += 60; - $diff->h--; - - if ($diff->h < 0) { - $diff->h += 24; - $diff->d--; - - if ($diff->d < 0) { - $diff->d += 30; - $diff->m--; - - if ($diff->m < 0) { - $diff->m += 12; - $diff->y--; - } - } - } - } - } - - return; - } - - $diff->f *= -1; - $diff->invert(); - } - - /** - * @param DateInterval $diff - * @param bool $absolute - * - * @return CarbonInterval - */ - protected static function fixDiffInterval(DateInterval $diff, $absolute, array $skip = []) - { - $diff = CarbonInterval::instance($diff, $skip); - - // Work-around for https://bugs.php.net/bug.php?id=77145 - // @codeCoverageIgnoreStart - if ($diff->f > 0 && $diff->y === -1 && $diff->m === 11 && $diff->d >= 27 && $diff->h === 23 && $diff->i === 59 && $diff->s === 59) { - $diff->y = 0; - $diff->m = 0; - $diff->d = 0; - $diff->h = 0; - $diff->i = 0; - $diff->s = 0; - $diff->f = (1000000 - round($diff->f * 1000000)) / 1000000; - $diff->invert(); - } elseif ($diff->f < 0) { - static::fixNegativeMicroseconds($diff); - } - // @codeCoverageIgnoreEnd - - if ($absolute && $diff->invert) { - $diff->invert(); - } - - return $diff; - } - /** * Get the difference as a DateInterval instance. * Return relative interval (negative if $absolute flag is not set to true and the given date is before @@ -120,8 +47,7 @@ trait Difference * * @return DateInterval */ - #[ReturnTypeWillChange] - public function diff($date = null, $absolute = false) + public function diffAsDateInterval($date = null, bool $absolute = false): DateInterval { $other = $this->resolveCarbon($date); @@ -129,13 +55,41 @@ trait Difference // It was initially introduced for https://bugs.php.net/bug.php?id=80998 // The very specific case of 80998 was fixed in PHP 8.1beta3, but it introduced 81458 // So we still need to keep this for now - // @codeCoverageIgnoreStart - if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && $other->tz !== $this->tz) { - $other = $other->avoidMutation()->tz($this->tz); + if ($other->tz !== $this->tz) { + $other = $other->avoidMutation()->setTimezone($this->tz); } - // @codeCoverageIgnoreEnd - return parent::diff($other, (bool) $absolute); + return parent::diff($other, $absolute); + } + + /** + * Get the difference as a \Time\Duration instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return Duration + */ + public function diffAsDuration($date = null, bool $absolute = false): Duration + { + $other = $this->resolveCarbon($date); + + // Work-around for https://bugs.php.net/bug.php?id=81458 + // It was initially introduced for https://bugs.php.net/bug.php?id=80998 + // The very specific case of 80998 was fixed in PHP 8.1beta3, but it introduced 81458 + // So we still need to keep this for now + if ($other->tz !== $this->tz) { + $other = $other->avoidMutation()->setTimezone($this->tz); + } + + $duration = DurationConverter::createDurationFromSeconds( + $other->getTimestamp() - $this->getTimestamp(), + ($other->microsecond - $this->microsecond) * 1000, + ); + + return $absolute && $duration->negative ? $duration->negate() : $duration; } /** @@ -148,9 +102,49 @@ trait Difference * * @return CarbonInterval */ - public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = []) + public function diffAsCarbonInterval($date = null, bool $absolute = false, array $skip = []): CarbonInterval { - return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute, $skip); + return CarbonInterval::diff($this, $this->resolveCarbon($date), $absolute, $skip) + ->setLocalTranslator($this->getLocalTranslator()); + } + + /** + * @alias diffAsCarbonInterval + * + * Get the difference as a CarbonInterval instance. + * Return relative interval (negative if $absolute flag is not set to true and the given date is before + * current one). + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * + * @return CarbonInterval + */ + public function diff($date = null, bool $absolute = false, array $skip = []): CarbonInterval + { + return $this->diffAsCarbonInterval($date, $absolute, $skip); + } + + /** + * @param Unit|string $unit microsecond, millisecond, second, minute, + * hour, day, week, month, quarter, year, + * century, millennium + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) + * + * @return float + */ + public function diffInUnit(Unit|string $unit, $date = null, bool $absolute = false, bool $utc = false): float + { + $unit = static::pluralUnit($unit instanceof Unit ? $unit->value : rtrim($unit, 'z')); + $method = 'diffIn'.$unit; + + if (!method_exists($this, $method)) { + throw new UnknownUnitException($unit); + } + + return $this->$method($date, $absolute, $utc); } /** @@ -158,82 +152,165 @@ trait Difference * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) * - * @return int + * @return float */ - public function diffInYears($date = null, $absolute = true) + public function diffInYears($date = null, bool $absolute = false, bool $utc = false): float { - return (int) $this->diff($this->resolveCarbon($date), $absolute)->format('%r%y'); - } + $start = $this; + $end = $this->resolveCarbon($date); - /** - * Get the difference in quarters rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInQuarters($date = null, $absolute = true) - { - return (int) ($this->diffInMonths($date, $absolute) / static::MONTHS_PER_QUARTER); - } - - /** - * Get the difference in months rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInMonths($date = null, $absolute = true) - { - $date = $this->resolveCarbon($date)->avoidMutation()->tz($this->tz); - - [$yearStart, $monthStart, $dayStart] = explode('-', $this->format('Y-m-dHisu')); - [$yearEnd, $monthEnd, $dayEnd] = explode('-', $date->format('Y-m-dHisu')); - - $diff = (((int) $yearEnd) - ((int) $yearStart)) * static::MONTHS_PER_YEAR + - ((int) $monthEnd) - ((int) $monthStart); - - if ($diff > 0) { - $diff -= ($dayStart > $dayEnd ? 1 : 0); - } elseif ($diff < 0) { - $diff += ($dayStart < $dayEnd ? 1 : 0); + if ($utc) { + $start = $start->avoidMutation()->utc(); + $end = $end->avoidMutation()->utc(); } - return $absolute ? abs($diff) : $diff; + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + + $yearsDiff = (int) $start->diff($end, $absolute)->format('%r%y'); + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addYears($yearsDiff); + + if ($floorEnd >= $end) { + return $sign * $yearsDiff; + } + + /** @var Carbon|CarbonImmutable $ceilEnd */ + $ceilEnd = $start->avoidMutation()->addYears($yearsDiff + 1); + + $daysToFloor = $floorEnd->diffInDays($end); + $daysToCeil = $end->diffInDays($ceilEnd); + + return $sign * ($yearsDiff + $daysToFloor / ($daysToCeil + $daysToFloor)); } /** - * Get the difference in weeks rounded down. + * Get the difference in quarters. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) * - * @return int + * @return float */ - public function diffInWeeks($date = null, $absolute = true) + public function diffInQuarters($date = null, bool $absolute = false, bool $utc = false): float { - return (int) ($this->diffInDays($date, $absolute) / static::DAYS_PER_WEEK); + return $this->diffInMonths($date, $absolute, $utc) / static::MONTHS_PER_QUARTER; } /** - * Get the difference in days rounded down. + * Get the difference in months. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) * - * @return int + * @return float */ - public function diffInDays($date = null, $absolute = true) + public function diffInMonths($date = null, bool $absolute = false, bool $utc = false): float { - return $this->getIntervalDayDiff($this->diff($this->resolveCarbon($date), $absolute)); + $start = $this; + $end = $this->resolveCarbon($date); + + // Compare using UTC + if ($utc || ($end->timezoneName !== $start->timezoneName)) { + $start = $start->avoidMutation()->utc(); + $end = $end->avoidMutation()->utc(); + } + + [$yearStart, $monthStart, $dayStart] = explode('-', $start->format('Y-m-dHisu')); + [$yearEnd, $monthEnd, $dayEnd] = explode('-', $end->format('Y-m-dHisu')); + + $monthsDiff = (((int) $yearEnd) - ((int) $yearStart)) * static::MONTHS_PER_YEAR + + ((int) $monthEnd) - ((int) $monthStart); + + if ($monthsDiff > 0) { + $monthsDiff -= ($dayStart > $dayEnd ? 1 : 0); + } elseif ($monthsDiff < 0) { + $monthsDiff += ($dayStart < $dayEnd ? 1 : 0); + } + + $ascending = ($start <= $end); + $sign = $absolute || $ascending ? 1 : -1; + $monthsDiff = abs($monthsDiff); + + if (!$ascending) { + [$start, $end] = [$end, $start]; + } + + /** @var Carbon|CarbonImmutable $floorEnd */ + $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); + + if ($floorEnd >= $end) { + return $sign * $monthsDiff; + } + + /** @var Carbon|CarbonImmutable $ceilEnd */ + $ceilEnd = $start->avoidMutation()->addMonths($monthsDiff + 1); + + $daysToFloor = $floorEnd->diffInDays($end); + $daysToCeil = $end->diffInDays($ceilEnd); + + return $sign * ($monthsDiff + $daysToFloor / ($daysToCeil + $daysToFloor)); } /** - * Get the difference in days using a filter closure rounded down. + * Get the difference in weeks. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) + * + * @return float + */ + public function diffInWeeks($date = null, bool $absolute = false, bool $utc = false): float + { + return $this->diffInDays($date, $absolute, $utc) / static::DAYS_PER_WEEK; + } + + /** + * Get the difference in days. + * + * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date + * @param bool $absolute Get the absolute of the difference + * @param bool $utc Always convert dates to UTC before comparing (if not set, it will do it only if timezones are different) + * + * @return float + */ + public function diffInDays($date = null, bool $absolute = false, bool $utc = false): float + { + $date = $this->resolveCarbon($date); + $current = $this; + + // Compare using UTC + if ($utc || ($date->timezoneName !== $current->timezoneName)) { + $date = $date->avoidMutation()->utc(); + $current = $current->avoidMutation()->utc(); + } + + $negative = ($date < $current); + [$start, $end] = $negative ? [$date, $current] : [$current, $date]; + $interval = $start->diffAsDateInterval($end); + $daysA = $this->getIntervalDayDiff($interval); + $floorEnd = $start->avoidMutation()->addDays($daysA); + $daysB = $daysA + ($floorEnd <= $end ? 1 : -1); + $ceilEnd = $start->avoidMutation()->addDays($daysB); + $microsecondsBetween = $floorEnd->diffInMicroseconds($ceilEnd); + $microsecondsToEnd = $floorEnd->diffInMicroseconds($end); + + return ($negative && !$absolute ? -1 : 1) + * ($daysA * ($microsecondsBetween - $microsecondsToEnd) + $daysB * $microsecondsToEnd) + / $microsecondsBetween; + } + + /** + * Get the difference in days using a filter closure. * * @param Closure $callback * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date @@ -241,13 +318,13 @@ trait Difference * * @return int */ - public function diffInDaysFiltered(Closure $callback, $date = null, $absolute = true) + public function diffInDaysFiltered(Closure $callback, $date = null, bool $absolute = false): int { return $this->diffFiltered(CarbonInterval::day(), $callback, $date, $absolute); } /** - * Get the difference in hours using a filter closure rounded down. + * Get the difference in hours using a filter closure. * * @param Closure $callback * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date @@ -255,7 +332,7 @@ trait Difference * * @return int */ - public function diffInHoursFiltered(Closure $callback, $date = null, $absolute = true) + public function diffInHoursFiltered(Closure $callback, $date = null, bool $absolute = false): int { return $this->diffFiltered(CarbonInterval::hour(), $callback, $date, $absolute); } @@ -270,7 +347,7 @@ trait Difference * * @return int */ - public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, $absolute = true) + public function diffFiltered(CarbonInterval $ci, Closure $callback, $date = null, bool $absolute = false): int { $start = $this; $end = $this->resolveCarbon($date); @@ -289,109 +366,76 @@ trait Difference } /** - * Get the difference in weekdays rounded down. + * Get the difference in weekdays. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * * @return int */ - public function diffInWeekdays($date = null, $absolute = true) + public function diffInWeekdays($date = null, bool $absolute = false): int { - return $this->diffInDaysFiltered(static function (CarbonInterface $date) { - return $date->isWeekday(); - }, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute); + return $this->diffInDaysFiltered( + static fn (CarbonInterface $date) => $date->isWeekday(), + $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), + $absolute, + ); } /** - * Get the difference in weekend days using a filter rounded down. + * Get the difference in weekend days using a filter. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * * @return int */ - public function diffInWeekendDays($date = null, $absolute = true) + public function diffInWeekendDays($date = null, bool $absolute = false): int { - return $this->diffInDaysFiltered(static function (CarbonInterface $date) { - return $date->isWeekend(); - }, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute); + return $this->diffInDaysFiltered( + static fn (CarbonInterface $date) => $date->isWeekend(), + $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), + $absolute, + ); } /** - * Get the difference in hours rounded down. + * Get the difference in hours. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * - * @return int + * @return float */ - public function diffInHours($date = null, $absolute = true) + public function diffInHours($date = null, bool $absolute = false): float { - return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + return $this->diffInMinutes($date, $absolute) / static::MINUTES_PER_HOUR; } /** - * Get the difference in hours rounded down using timestamps. + * Get the difference in minutes. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * - * @return int + * @return float */ - public function diffInRealHours($date = null, $absolute = true) + public function diffInMinutes($date = null, bool $absolute = false): float { - return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE / static::MINUTES_PER_HOUR); + return $this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; } /** - * Get the difference in minutes rounded down. + * Get the difference in seconds. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * - * @return int + * @return float */ - public function diffInMinutes($date = null, $absolute = true) + public function diffInSeconds($date = null, bool $absolute = false): float { - return (int) ($this->diffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); - } - - /** - * Get the difference in minutes rounded down using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealMinutes($date = null, $absolute = true) - { - return (int) ($this->diffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE); - } - - /** - * Get the difference in seconds rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInSeconds($date = null, $absolute = true) - { - $diff = $this->diff($date); - - if ($diff->days === 0) { - $diff = static::fixDiffInterval($diff, $absolute); - } - - $value = (((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + - $diff->h) * static::MINUTES_PER_HOUR + - $diff->i) * static::SECONDS_PER_MINUTE + - $diff->s; - - return $absolute || !$diff->invert ? $value : -$value; + return $this->diffInMilliseconds($date, $absolute) / static::MILLISECONDS_PER_SECOND; } /** @@ -400,58 +444,9 @@ trait Difference * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * - * @return int + * @return float */ - public function diffInMicroseconds($date = null, $absolute = true) - { - $diff = $this->diff($date); - $value = (int) round(((((($diff->m || $diff->y ? $diff->days : $diff->d) * static::HOURS_PER_DAY) + - $diff->h) * static::MINUTES_PER_HOUR + - $diff->i) * static::SECONDS_PER_MINUTE + - ($diff->f + $diff->s)) * static::MICROSECONDS_PER_SECOND); - - return $absolute || !$diff->invert ? $value : -$value; - } - - /** - * Get the difference in milliseconds rounded down. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInMilliseconds($date = null, $absolute = true) - { - return (int) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); - } - - /** - * Get the difference in seconds using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealSeconds($date = null, $absolute = true) - { - /** @var CarbonInterface $date */ - $date = $this->resolveCarbon($date); - $value = $date->getTimestamp() - $this->getTimestamp(); - - return $absolute ? abs($value) : $value; - } - - /** - * Get the difference in microseconds using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealMicroseconds($date = null, $absolute = true) + public function diffInMicroseconds($date = null, bool $absolute = false): float { /** @var CarbonInterface $date */ $date = $this->resolveCarbon($date); @@ -462,319 +457,36 @@ trait Difference } /** - * Get the difference in milliseconds rounded down using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return int - */ - public function diffInRealMilliseconds($date = null, $absolute = true) - { - return (int) ($this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND); - } - - /** - * Get the difference in seconds as float (microsecond-precision). + * Get the difference in milliseconds. * * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date * @param bool $absolute Get the absolute of the difference * * @return float */ - public function floatDiffInSeconds($date = null, $absolute = true) + public function diffInMilliseconds($date = null, bool $absolute = false): float { - return (float) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND); - } - - /** - * Get the difference in minutes as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInMinutes($date = null, $absolute = true) - { - return $this->floatDiffInSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; - } - - /** - * Get the difference in hours as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInHours($date = null, $absolute = true) - { - return $this->floatDiffInMinutes($date, $absolute) / static::MINUTES_PER_HOUR; - } - - /** - * Get the difference in days as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInDays($date = null, $absolute = true) - { - $hoursDiff = $this->floatDiffInHours($date, $absolute); - $interval = $this->diff($date, $absolute); - - if ($interval->y === 0 && $interval->m === 0 && $interval->d === 0) { - return $hoursDiff / static::HOURS_PER_DAY; - } - - $daysDiff = $this->getIntervalDayDiff($interval); - - return $daysDiff + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; - } - - /** - * Get the difference in weeks as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInWeeks($date = null, $absolute = true) - { - return $this->floatDiffInDays($date, $absolute) / static::DAYS_PER_WEEK; - } - - /** - * Get the difference in months as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInMonths($date = null, $absolute = true) - { - $start = $this; - $end = $this->resolveCarbon($date); - $ascending = ($start <= $end); - $sign = $absolute || $ascending ? 1 : -1; - if (!$ascending) { - [$start, $end] = [$end, $start]; - } - $monthsDiff = $start->diffInMonths($end); - /** @var Carbon|CarbonImmutable $floorEnd */ - $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); - - if ($floorEnd >= $end) { - return $sign * $monthsDiff; - } - - /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ - $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); - - if ($startOfMonthAfterFloorEnd > $end) { - return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInMonth); - } - - return $sign * ($monthsDiff + $floorEnd->floatDiffInDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInDays($end) / $end->daysInMonth); - } - - /** - * Get the difference in year as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInYears($date = null, $absolute = true) - { - $start = $this; - $end = $this->resolveCarbon($date); - $ascending = ($start <= $end); - $sign = $absolute || $ascending ? 1 : -1; - if (!$ascending) { - [$start, $end] = [$end, $start]; - } - $yearsDiff = $start->diffInYears($end); - /** @var Carbon|CarbonImmutable $floorEnd */ - $floorEnd = $start->avoidMutation()->addYears($yearsDiff); - - if ($floorEnd >= $end) { - return $sign * $yearsDiff; - } - - /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ - $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); - - if ($startOfYearAfterFloorEnd > $end) { - return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($end) / $floorEnd->daysInYear); - } - - return $sign * ($yearsDiff + $floorEnd->floatDiffInDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInDays($end) / $end->daysInYear); - } - - /** - * Get the difference in seconds as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealSeconds($date = null, $absolute = true) - { - return $this->diffInRealMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND; - } - - /** - * Get the difference in minutes as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealMinutes($date = null, $absolute = true) - { - return $this->floatDiffInRealSeconds($date, $absolute) / static::SECONDS_PER_MINUTE; - } - - /** - * Get the difference in hours as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealHours($date = null, $absolute = true) - { - return $this->floatDiffInRealMinutes($date, $absolute) / static::MINUTES_PER_HOUR; - } - - /** - * Get the difference in days as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealDays($date = null, $absolute = true) - { - $date = $this->resolveUTC($date); - $utc = $this->avoidMutation()->utc(); - $hoursDiff = $utc->floatDiffInRealHours($date, $absolute); - - return ($hoursDiff < 0 ? -1 : 1) * $utc->diffInDays($date) + fmod($hoursDiff, static::HOURS_PER_DAY) / static::HOURS_PER_DAY; - } - - /** - * Get the difference in weeks as float (microsecond-precision). - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealWeeks($date = null, $absolute = true) - { - return $this->floatDiffInRealDays($date, $absolute) / static::DAYS_PER_WEEK; - } - - /** - * Get the difference in months as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealMonths($date = null, $absolute = true) - { - $start = $this; - $end = $this->resolveCarbon($date); - $ascending = ($start <= $end); - $sign = $absolute || $ascending ? 1 : -1; - if (!$ascending) { - [$start, $end] = [$end, $start]; - } - $monthsDiff = $start->diffInMonths($end); - /** @var Carbon|CarbonImmutable $floorEnd */ - $floorEnd = $start->avoidMutation()->addMonths($monthsDiff); - - if ($floorEnd >= $end) { - return $sign * $monthsDiff; - } - - /** @var Carbon|CarbonImmutable $startOfMonthAfterFloorEnd */ - $startOfMonthAfterFloorEnd = $floorEnd->avoidMutation()->addMonth()->startOfMonth(); - - if ($startOfMonthAfterFloorEnd > $end) { - return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInMonth); - } - - return $sign * ($monthsDiff + $floorEnd->floatDiffInRealDays($startOfMonthAfterFloorEnd) / $floorEnd->daysInMonth + $startOfMonthAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInMonth); - } - - /** - * Get the difference in year as float (microsecond-precision) using timestamps. - * - * @param \Carbon\CarbonInterface|\DateTimeInterface|string|null $date - * @param bool $absolute Get the absolute of the difference - * - * @return float - */ - public function floatDiffInRealYears($date = null, $absolute = true) - { - $start = $this; - $end = $this->resolveCarbon($date); - $ascending = ($start <= $end); - $sign = $absolute || $ascending ? 1 : -1; - if (!$ascending) { - [$start, $end] = [$end, $start]; - } - $yearsDiff = $start->diffInYears($end); - /** @var Carbon|CarbonImmutable $floorEnd */ - $floorEnd = $start->avoidMutation()->addYears($yearsDiff); - - if ($floorEnd >= $end) { - return $sign * $yearsDiff; - } - - /** @var Carbon|CarbonImmutable $startOfYearAfterFloorEnd */ - $startOfYearAfterFloorEnd = $floorEnd->avoidMutation()->addYear()->startOfYear(); - - if ($startOfYearAfterFloorEnd > $end) { - return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($end) / $floorEnd->daysInYear); - } - - return $sign * ($yearsDiff + $floorEnd->floatDiffInRealDays($startOfYearAfterFloorEnd) / $floorEnd->daysInYear + $startOfYearAfterFloorEnd->floatDiffInRealDays($end) / $end->daysInYear); + return $this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_MILLISECOND; } /** * The number of seconds since midnight. * - * @return int + * @return float */ - public function secondsSinceMidnight() + public function secondsSinceMidnight(): float { - return $this->diffInSeconds($this->avoidMutation()->startOfDay()); + return $this->diffInSeconds($this->copy()->startOfDay(), true); } /** * The number of seconds until 23:59:59. * - * @return int + * @return float */ - public function secondsUntilEndOfDay() + public function secondsUntilEndOfDay(): float { - return $this->diffInSeconds($this->avoidMutation()->endOfDay()); + return $this->diffInSeconds($this->copy()->endOfDay(), true); } /** @@ -790,41 +502,44 @@ trait Difference * echo Carbon::tomorrow()->diffForHumans(Carbon::yesterday(), ['short' => true]) . "\n"; * ``` * - * @param Carbon|\DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; - * if null passed, now will be used as comparison reference; - * if any other type, it will be converted to date and used as reference. - * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: - * - 'syntax' entry (see below) - * - 'short' entry (see below) - * - 'parts' entry (see below) - * - 'options' entry (see below) - * - 'skip' entry, list of units to skip (array of strings or a single string, - * ` it can be the unit name (singular or plural) or its shortcut - * ` (y, m, w, d, h, min, s, ms, µs). - * - 'aUnit' entry, prefer "an hour" over "1 hour" if true - * - 'join' entry determines how to join multiple parts of the string - * ` - if $join is a string, it's used as a joiner glue - * ` - if $join is a callable/closure, it get the list of string and should return a string - * ` - if $join is an array, the first item will be the default glue, and the second item - * ` will be used instead of the glue for the last item - * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) - * ` - if $join is missing, a space will be used as glue - * - 'other' entry (see above) - * - 'minimumUnit' entry determines the smallest unit of time to display can be long or - * ` short form of the units, e.g. 'hour' or 'h' (default value: s) - * if int passed, it add modifiers: - * Possible values: - * - CarbonInterface::DIFF_ABSOLUTE no modifiers - * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier - * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier - * Default value: CarbonInterface::DIFF_ABSOLUTE - * @param bool $short displays short format of time units - * @param int $parts maximum number of parts to display (default value: 1: single unit) - * @param int $options human diff options - * - * @return string + * @param Carbon|DateTimeInterface|string|array|null $other if array passed, will be used as parameters array, see $syntax below; + * if null passed, now will be used as comparison reference; + * if any other type, it will be converted to date and used as reference. + * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains: + * ⦿ 'syntax' entry (see below) + * ⦿ 'short' entry (see below) + * ⦿ 'parts' entry (see below) + * ⦿ 'options' entry (see below) + * ⦿ 'skip' entry, list of units to skip (array of strings or a single string, + * ` it can be the unit name (singular or plural) or its shortcut + * ` (y, m, w, d, h, min, s, ms, µs). + * ⦿ 'aUnit' entry, prefer "an hour" over "1 hour" if true + * ⦿ 'altNumbers' entry, use alternative numbers if available + * ` (from the current language if true is passed, from the given language(s) + * ` if array or string is passed) + * ⦿ 'join' entry determines how to join multiple parts of the string + * ` - if $join is a string, it's used as a joiner glue + * ` - if $join is a callable/closure, it get the list of string and should return a string + * ` - if $join is an array, the first item will be the default glue, and the second item + * ` will be used instead of the glue for the last item + * ` - if $join is true, it will be guessed from the locale ('list' translation file entry) + * ` - if $join is missing, a space will be used as glue + * ⦿ 'other' entry (see above) + * ⦿ 'minimumUnit' entry determines the smallest unit of time to display can be long or + * ` short form of the units, e.g. 'hour' or 'h' (default value: s) + * ⦿ 'locale' language in which the diff should be output (has no effect if 'translator' key is set) + * ⦿ 'translator' a custom translator to use to translator the output. + * if int passed, it adds modifiers: + * Possible values: + * - CarbonInterface::DIFF_ABSOLUTE no modifiers + * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier + * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier + * Default value: CarbonInterface::DIFF_ABSOLUTE + * @param bool $short displays short format of time units + * @param int $parts maximum number of parts to display (default value: 1: single unit) + * @param int $options human diff options */ - public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null) + public function diffForHumans($other = null, $syntax = null, $short = false, $parts = 1, $options = null): string { /* @var CarbonInterface $this */ if (\is_array($other)) { @@ -834,19 +549,22 @@ trait Difference } $intSyntax = &$syntax; + if (\is_array($syntax)) { $syntax['syntax'] = $syntax['syntax'] ?? null; $intSyntax = &$syntax['syntax']; } + $intSyntax = (int) ($intSyntax ?? static::DIFF_RELATIVE_AUTO); $intSyntax = $intSyntax === static::DIFF_RELATIVE_AUTO && $other === null ? static::DIFF_RELATIVE_TO_NOW : $intSyntax; $parts = min(7, max(1, (int) $parts)); $skip = \is_array($syntax) ? ($syntax['skip'] ?? []) : []; + $options ??= $this->localHumanDiffOptions ?? $this->transmitFactory( + static fn () => static::getHumanDiffOptions(), + ); - return $this->diffAsCarbonInterval($other, false, (array) $skip) - ->setLocalTranslator($this->getLocalTranslator()) - ->forHumans($syntax, (bool) $short, $parts, $options ?? $this->localHumanDiffOptions ?? static::getHumanDiffOptions()); + return $this->diff($other, skip: (array) $skip)->forHumans($syntax, (bool) $short, $parts, $options); } /** @@ -1107,22 +825,21 @@ trait Difference } /** - * Get the difference in a human readable format in the current locale from current instance to an other + * Get the difference in a human-readable format in the current locale from current instance to another * instance given (or now if null given). * * @return string */ - public function timespan($other = null, $timezone = null) + public function timespan($other = null, $timezone = null): string { - if (!$other instanceof DateTimeInterface) { - $other = static::parse($other, $timezone); + if (\is_string($other)) { + $other = $this->transmitFactory(static fn () => static::parse($other, $timezone)); } return $this->diffForHumans($other, [ 'join' => ', ', 'syntax' => CarbonInterface::DIFF_ABSOLUTE, - 'options' => CarbonInterface::NO_ZERO_DIFF, - 'parts' => -1, + 'parts' => INF, ]); } @@ -1144,12 +861,12 @@ trait Difference /** @var CarbonInterface $other */ $other = $this->resolveCarbon($referenceTime)->avoidMutation()->setTimezone($this->getTimezone())->startOfDay(); $diff = $other->diffInDays($current, false); - $format = $diff < -6 ? 'sameElse' : ( + $format = $diff <= -static::DAYS_PER_WEEK ? 'sameElse' : ( $diff < -1 ? 'lastWeek' : ( $diff < 0 ? 'lastDay' : ( $diff < 1 ? 'sameDay' : ( $diff < 2 ? 'nextDay' : ( - $diff < 7 ? 'nextWeek' : 'sameElse' + $diff < static::DAYS_PER_WEEK ? 'nextWeek' : 'sameElse' ) ) ) @@ -1165,18 +882,6 @@ trait Difference private function getIntervalDayDiff(DateInterval $interval): int { - $daysDiff = (int) $interval->format('%a'); - $sign = $interval->format('%r') === '-' ? -1 : 1; - - if (\is_int($interval->days) && - $interval->y === 0 && - $interval->m === 0 && - version_compare(PHP_VERSION, '8.1.0-dev', '<') && - abs($interval->d - $daysDiff) === 1 - ) { - $daysDiff = abs($interval->d); // @codeCoverageIgnore - } - - return $daysDiff * $sign; + return (int) $interval->format('%r%a'); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Eras.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Eras.php new file mode 100644 index 000000000..a7ca76cd8 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Eras.php @@ -0,0 +1,351 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\CarbonInterface; +use Carbon\Exceptions\InvalidFormatException; +use Carbon\Translator; +use InvalidArgumentException; +use Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Trait Eras. + * + * Era (moment.js style) helpers, used to read and describe the eras of the current locale. + */ +trait Eras +{ + /** + * Return the era of the current locale the current date is in (null if none). + * + * Eras follow moment.js: a list of ['since', 'until', 'offset', 'name', 'narrow', 'abbr'] where since + * and until are 'YYYY-MM-DD' dates (or INF/-INF for until) and since may be after until for eras counting + * years backward (such as BC). + * + * @return array{name: string|null, narrow: string|null, abbr: string|null, offset: int, sinceYear: int, untilYear: int|float, direction: int}|null + */ + private function getEra(): ?array + { + $date = $this->year * 10000 + $this->month * 100 + $this->day; + + foreach ($this->getEras() as $era) { + $description = self::describeEra($era); + + if (($description['since'] <= $date && $date <= $description['until']) || ($description['until'] <= $date && $date <= $description['since'])) { + unset($description['since'], $description['until']); + + return $description; + } + } + + return null; + } + + /** + * @param array $era + * + * @return array{name: string|null, narrow: string|null, abbr: string|null, offset: int, sinceYear: int, untilYear: int|float, direction: int, since: int|float, until: int|float} + */ + private static function describeEra(array $era): array + { + [$sinceYear, $since] = self::parseEraDate($era['since']); + [$untilYear, $until] = self::parseEraDate($era['until'] ?? INF); + + return [ + 'name' => $era['name'] ?? null, + 'narrow' => $era['narrow'] ?? null, + 'abbr' => $era['abbr'] ?? null, + 'offset' => (int) ($era['offset'] ?? 1), + 'sinceYear' => $sinceYear, + 'untilYear' => $untilYear, + 'direction' => $since <= $until ? 1 : -1, + 'since' => $since, + 'until' => $until, + ]; + } + + /** + * Return the "eras" of the current locale, taken as a whole from the first locale of the fallback chain + * defining them, so a locale with fewer eras does not get the extra ones of its fallback. + * Like in moment.js, Anno Domini / Before Christ are used if no locale defines eras. + * + * @return array> + */ + private function getEras(): array + { + $translator = $this->getLocalTranslator(); + + if (!($translator instanceof TranslatorBagInterface)) { + return self::getDefaultEras(); + } + + $locale = $translator instanceof LocaleAwareInterface ? $translator->getLocale() : null; + + for ($catalogue = $translator->getCatalogue($locale); $catalogue; $catalogue = $catalogue->getFallbackCatalogue()) { + if (!$catalogue->defines('eras.0.since')) { + continue; + } + + $eras = []; + + for ($index = 0; $catalogue->defines("eras.$index.since"); $index++) { + $era = []; + + foreach (['since', 'until', 'offset', 'name', 'narrow', 'abbr'] as $field) { + $key = "eras.$index.$field"; + + if ($catalogue->defines($key)) { + $era[$field] = self::getFromCatalogue($translator, $catalogue, $key); + } + } + + $eras[] = $era; + } + + return $eras; + } + + return self::getDefaultEras(); + } + + /** + * @return array> + */ + private static function getDefaultEras(): array + { + return [ + [ + 'since' => '0001-01-01', + 'until' => INF, + 'offset' => 1, + 'name' => 'Anno Domini', + 'narrow' => 'AD', + 'abbr' => 'AD', + ], + [ + 'since' => '0000-12-31', + 'until' => -INF, + 'offset' => 1, + 'name' => 'Before Christ', + 'narrow' => 'BC', + 'abbr' => 'BC', + ], + ]; + } + + /** + * Parse an era limit ('YYYY-MM-DD', the year may be negative, or INF/-INF) into its year and a sortable value. + * + * @return array{0: int|float, 1: int|float} + */ + private static function parseEraDate(mixed $date): array + { + if (\is_float($date) || \is_int($date)) { + return [$date, $date]; + } + + if (!preg_match('/^([+-]?\d+)-(\d{1,2})-(\d{1,2})$/', (string) $date, $match)) { + throw new InvalidArgumentException("Invalid era date '$date', expected YYYY-MM-DD."); + } + + $year = (int) $match[1]; + + return [$year, $year * 10000 + (int) $match[2] * 100 + (int) $match[3]]; + } + + /** + * Replace the era tokens of an ISO format (and what they match in $time) by a Gregorian year, so the result + * can be parsed by rawCreateFromFormat(). The era year is converted before parsing so a leap day is checked + * against the right year (BE 2567-02-29 is valid, but CE 2567-02-29 is not). + * + * @throws InvalidFormatException + * + * @return array{0: string, 1: string} format and time without era tokens + */ + private static function resolveIsoFormatEras(string $format, string $time, ?string $locale, ?TranslatorInterface $translator): array + { + // The "u" modifier below would fail with a format that is not valid UTF-8, it's left as before era tokens + if (strpbrk($format, 'Ny') === false || !preg_match('//u', $format)) { + return [$format, $time]; + } + + preg_match_all('/\\\\.|'.CarbonInterface::ISO_FORMAT_REGEXP.'|./su', $format, $matches); + + $tokens = $matches[0]; + $isEraToken = static fn (string $token): bool => $token[0] === 'N' || $token[0] === 'y'; + + if (!array_filter($tokens, $isEraToken)) { + return [$format, $time]; + } + + $date = static::now()->setLocalTranslator($translator ?? Translator::get($locale)); + $eras = array_map(self::describeEra(...), $date->getEras()); + $pattern = ''; + $generic = ''; + $eraTokens = []; + + foreach ($tokens as $index => $token) { + if ($isEraToken($token)) { + $eraTokens[$index] = $token; + $eraPattern = $token[0] === 'N' ? self::getEraNamesPattern($eras, $token) : ($token === 'yo' ? '[0-9]+\\p{L}*' : '[0-9]+'); + $pattern .= "($eraPattern)"; + $generic .= $token[0] === 'N' ? '(.+?)' : '([0-9]+\\p{L}*)'; + + continue; + } + + $part = self::getIsoTokenPattern($token); + $pattern .= $part; + $generic .= $part; + } + + $captures = []; + + if (!preg_match("/^$pattern\$/siu", $time, $captures, \PREG_OFFSET_CAPTURE)) { + if (preg_match("/^$generic\$/siu", $time, $captures)) { + foreach (array_keys($eraTokens) as $position => $index) { + if ($tokens[$index][0] === 'N') { + throw new InvalidFormatException( + "Unknown era '".$captures[$position + 1]."' for locale '".$date->locale."' in '$time'.", + ); + } + } + } + + throw new InvalidFormatException("Could not parse '$time' with format '$format'."); + } + + $era = null; + $position = 1; + + foreach ($eraTokens as $token) { + $text = $captures[$position++][0]; + + if ($token[0] === 'N') { + $era ??= self::findEraByName($eras, $token, $text); + } + } + + $era ??= $date->getEra() ?? throw new InvalidFormatException("No era found today in locale '{$date->locale}'."); + $replacements = []; + $position = 1; + + foreach ($eraTokens as $index => $token) { + [$text, $offset] = $captures[$position++]; + + if ($token[0] === 'N') { + $replacements[$offset] = [\strlen($text), '']; + + continue; + } + + $eraYear = (int) $text; + $year = $era['sinceYear'] + ($eraYear - $era['offset']) * $era['direction']; + + if (($year - $era['sinceYear']) * $era['direction'] < 0 + || (\is_int($era['untilYear']) && ($year - $era['untilYear']) * $era['direction'] > 0) + ) { + throw new InvalidFormatException("Year $eraYear is out of the era '".($era['abbr'] ?? $era['name'] ?? '')."'."); + } + + if ($year < 0 || $year > 9999) { + throw new InvalidFormatException("Year $eraYear of the era '".($era['abbr'] ?? $era['name'] ?? '')."' is Gregorian year $year, only years from 0 to 9999 can be created."); + } + + $replacements[$offset] = [\strlen($text), \sprintf('%04d', $year)]; + $tokens[$index] = 'YYYY'; + } + + krsort($replacements); + + foreach ($replacements as $offset => [$length, $replacement]) { + $time = substr_replace($time, $replacement, $offset, $length); + } + + foreach ($eraTokens as $index => $token) { + if ($token[0] === 'N') { + $tokens[$index] = ''; + } + } + + return [implode('', $tokens), $time]; + } + + /** + * Pattern matching the era names (or abbreviations, or narrow names depending on the token) of the given eras. + * + * @param array> $eras + */ + private static function getEraNamesPattern(array $eras, string $token): string + { + $names = array_unique(array_filter(array_column($eras, self::getEraField($token)), 'is_string')); + usort($names, static fn (string $first, string $second) => \strlen($second) <=> \strlen($first)); + + return $names ? implode('|', array_map(static fn (string $name) => preg_quote($name, '/'), $names)) : '(?!)'; + } + + /** + * @param array> $eras + * + * @return array + */ + private static function findEraByName(array $eras, string $token, string $name): array + { + $field = self::getEraField($token); + + foreach ($eras as $era) { + if (\is_string($era[$field]) && mb_strtolower($era[$field]) === mb_strtolower($name)) { + return $era; + } + } + + // The pattern only matches the names of these eras + throw new InvalidFormatException("Unknown era '$name'."); // @codeCoverageIgnore + } + + private static function getEraField(string $token): string + { + return match (\strlen($token)) { + 4 => 'name', + 5 => 'narrow', + default => 'abbr', + }; + } + + /** + * Pattern used to locate the era in a string, only to read what is around the era tokens (PHP does the parsing). + */ + private static function getIsoTokenPattern(string $token): string + { + if ($token[0] === '\\') { + return preg_quote(substr($token, 1), '/'); + } + + return match (true) { + $token === 'YY' => '[0-9]{2}', + (bool) preg_match('/^(?:Y+|x|X)$/', $token) => '[+-]?[0-9]+', + (bool) preg_match('/^(?:DDD[Do]?|[Hh]mm(?:ss)?|S+|O[YMDHhms])$/', $token) => '[0-9]+', + (bool) preg_match('/^(?:[DMHhkms]{1,2}|[MD]o)$/', $token) => '[0-9]{1,2}', + $token === ' ' => '\s', + $token === '?' => '.', + $token === '!' || $token === '|' => '', + $token === '+' => '.*', + $token === '*' => '.*?', + \strlen($token) === 1 && !ctype_alpha($token) => preg_quote($token, '/'), + default => '.+?', + }; + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php index f069c280d..47dfbb176 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalRounding.php @@ -1,5 +1,7 @@ forHumans(['locale' => 'en']); } if (\is_string($precision) && preg_match('/^\s*(?\d+)?\s*(?\w+)(?\W.*)?$/', $precision, $match)) { diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php index 82d7c3264..9e6177086 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/IntervalStep.php @@ -1,5 +1,7 @@ resolveCarbon($dateTime); + $carbonDate = $this->carbonOrResolve($dateTime); if ($this->step) { - return $carbonDate->setDateTimeFrom(($this->step)($carbonDate->avoidMutation(), $negated)); + $carbonDate = Callback::parameter($this->step, $carbonDate->avoidMutation()); + + return $carbonDate->modify(($this->step)($carbonDate, $negated)->format('Y-m-d H:i:s.u e O')); } if ($negated) { @@ -77,12 +82,8 @@ trait IntervalStep /** * Convert DateTimeImmutable instance to CarbonImmutable instance and DateTime instance to Carbon instance. - * - * @param DateTimeInterface $dateTime - * - * @return Carbon|CarbonImmutable */ - private function resolveCarbon(DateTimeInterface $dateTime) + private function resolveCarbon(DateTimeInterface $dateTime): Carbon|CarbonImmutable { if ($dateTime instanceof DateTimeImmutable) { return CarbonImmutable::instance($dateTime); @@ -90,4 +91,23 @@ trait IntervalStep return Carbon::instance($dateTime); } + + private function carbonOrResolve(mixed $dateTime): CarbonInterface + { + return $dateTime instanceof CarbonInterface + ? $dateTime + : $this->resolveCarbon($dateTime); + } + + private function checkNoStepIsDefined(string $method): void + { + if ($this->step !== null) { + $chunks = explode('::', $method, 2); + $method = $chunks[1] ?? $method; + + throw new BadMethodCallException( + "->$method() cannot be called on an interval with a step", + ); + } + } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/LocalFactory.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/LocalFactory.php new file mode 100644 index 000000000..a03985498 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/LocalFactory.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\Factory; +use Carbon\FactoryImmutable; +use Carbon\WrapperClock; +use Closure; + +/** + * Remember the factory that was the current at the creation of the object. + */ +trait LocalFactory +{ + /** + * The clock that generated the current instance (or FactoryImmutable::getDefaultInstance() if none) + */ + private ?WrapperClock $clock = null; + + public function getClock(): ?WrapperClock + { + return $this->clock; + } + + private function initLocalFactory(): void + { + $this->clock = FactoryImmutable::getCurrentClock(); + } + + /** + * Trigger the given action using the local factory of the object, so it will be transmitted + * to any object also using this trait and calling initLocalFactory() in its constructor. + * + * @template T + * + * @param Closure(): T $action + * + * @return T + */ + private function transmitFactory(Closure $action): mixed + { + $previousClock = FactoryImmutable::getCurrentClock(); + FactoryImmutable::setCurrentClock($this->clock); + + try { + return $action(); + } finally { + FactoryImmutable::setCurrentClock($previousClock); + } + } + + private function getFactory(): Factory + { + return $this->getClock()?->getFactory() ?? FactoryImmutable::getDefaultInstance(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php index 46aff113e..4c6a6eba3 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Localization.php @@ -1,5 +1,7 @@ settings() method. - * @see settings - * - * @param int $humanDiffOptions - */ - public static function setHumanDiffOptions($humanDiffOptions) - { - static::$humanDiffOptions = $humanDiffOptions; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @see settings - * - * @param int $humanDiffOption - */ - public static function enableHumanDiffOption($humanDiffOption) - { - static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @see settings - * - * @param int $humanDiffOption - */ - public static function disableHumanDiffOption($humanDiffOption) - { - static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; - } - - /** - * Return default humanDiff() options (merged flags as integer). - * - * @return int - */ - public static function getHumanDiffOptions() - { - return static::$humanDiffOptions; - } - - /** - * Get the default translator instance in use. - * - * @return \Symfony\Component\Translation\TranslatorInterface - */ - public static function getTranslator() - { - return static::translator(); - } - - /** - * Set the default translator instance to use. - * - * @param \Symfony\Component\Translation\TranslatorInterface $translator - * - * @return void - */ - public static function setTranslator(TranslatorInterface $translator) - { - static::$translator = $translator; - } + protected ?TranslatorInterface $localTranslator = null; /** * Return true if the current instance has its own translator. - * - * @return bool */ - public function hasLocalTranslator() + public function hasLocalTranslator(): bool { return isset($this->localTranslator); } /** * Get the translator of the current instance or the default if none set. - * - * @return \Symfony\Component\Translation\TranslatorInterface */ - public function getLocalTranslator() + public function getLocalTranslator(): TranslatorInterface { - return $this->localTranslator ?: static::translator(); + return $this->localTranslator ?? $this->transmitFactory(static fn () => static::getTranslator()); } /** * Set the translator for the current instance. - * - * @param \Symfony\Component\Translation\TranslatorInterface $translator - * - * @return $this */ - public function setLocalTranslator(TranslatorInterface $translator) + public function setLocalTranslator(TranslatorInterface $translator): self { $this->localTranslator = $translator; @@ -167,19 +67,19 @@ trait Localization /** * Returns raw translation message for a given key. * - * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use - * @param string $key key to find - * @param string|null $locale current locale used if null - * @param string|null $default default value if translation returns the key + * @param TranslatorInterface|null $translator the translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key * - * @return string + * @return string|Closure|null */ public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) { if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) { throw new InvalidTypeException( 'Translator does not implement '.TranslatorInterface::class.' and '.TranslatorBagInterface::class.'. '. - (\is_object($translator) ? \get_class($translator) : \gettype($translator)).' has been given.' + (\is_object($translator) ? \get_class($translator) : \gettype($translator)).' has been given.', ); } @@ -195,25 +95,25 @@ trait Localization /** * Returns raw translation message for a given key. * - * @param string $key key to find - * @param string|null $locale current locale used if null - * @param string|null $default default value if translation returns the key - * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use + * @param string $key key to find + * @param string|null $locale current locale used if null + * @param string|null $default default value if translation returns the key + * @param TranslatorInterface $translator an optional translator to use * * @return string */ public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) { - return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default); + return static::getTranslationMessageWith($translator ?? $this->getLocalTranslator(), $key, $locale, $default); } /** * Translate using translation string or callback available. * - * @param \Symfony\Component\Translation\TranslatorInterface $translator - * @param string $key - * @param array $parameters - * @param null $number + * @param TranslatorInterface $translator an optional translator to use + * @param string $key key to find + * @param array $parameters replacement parameters + * @param int|float|null $number number if plural * * @return string */ @@ -231,32 +131,31 @@ trait Localization $parameters[':count'] = $parameters['%count%']; } - // @codeCoverageIgnoreStart - $choice = $translator instanceof ContractsTranslatorInterface - ? $translator->trans($key, $parameters) - : $translator->transChoice($key, $number, $parameters); - // @codeCoverageIgnoreEnd - - return (string) $choice; + return (string) $translator->trans($key, $parameters); } /** * Translate using translation string or callback available. * - * @param string $key - * @param array $parameters - * @param string|int|float|null $number - * @param \Symfony\Component\Translation\TranslatorInterface|null $translator - * @param bool $altNumbers + * @param string $key key to find + * @param array $parameters replacement parameters + * @param string|int|float|null $number number if plural + * @param TranslatorInterface|null $translator an optional translator to use + * @param bool $altNumbers pass true to use alternative numbers * * @return string */ - public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = false): string - { - $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number); + public function translate( + string $key, + array $parameters = [], + string|int|float|null $number = null, + ?TranslatorInterface $translator = null, + bool $altNumbers = false, + ): string { + $translation = static::translateWith($translator ?? $this->getLocalTranslator(), $key, $parameters, $number); if ($number !== null && $altNumbers) { - return str_replace($number, $this->translateNumber($number), $translation); + return str_replace((string) $number, $this->translateNumber((int) $number), $translation); } return $translation; @@ -328,11 +227,15 @@ trait Localization * * @return string */ - public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) - { + public static function translateTimeString( + string $timeString, + ?string $from = null, + ?string $to = null, + int $mode = CarbonInterface::TRANSLATE_ALL, + ): string { // Fallback source and destination locales $from = $from ?: static::getLocale(); - $to = $to ?: 'en'; + $to = $to ?: CarbonInterface::DEFAULT_LOCALE; if ($from === $to) { return $timeString; @@ -366,24 +269,46 @@ trait Localization ); } + $processList = static fn (array $list): array => $list; + if ($key === 'from') { + $processList = static fn (array $list): array => array_map( + static fn (string $words): string => implode('|', array_map( + static fn (string $word): string => preg_quote($word, '/'), + explode('|', $words), + )), + $list, + ); + foreach (['months', 'weekdays'] as $variable) { $list = $messages[$variable.'_standalone'] ?? null; if ($list) { foreach ($$variable as $index => &$name) { - $name .= '|'.$messages[$variable.'_standalone'][$index]; + $name .= '|'.$list[$index]; } } } } + $monthTranslations = array_merge( + $mode & CarbonInterface::TRANSLATE_MONTHS + ? $processList(self::getTranslationArray($months, static::MONTHS_PER_YEAR, $timeString)) + : [], + $mode & CarbonInterface::TRANSLATE_MONTHS + ? $processList(self::getTranslationArray($messages['months_short'] ?? [], static::MONTHS_PER_YEAR, $timeString)) + : [], + ); + $$translationKey = array_merge( - $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [], - $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [], - $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [], - $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [], - $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys([ + $monthTranslations, + $mode & CarbonInterface::TRANSLATE_DAYS + ? $processList(self::getTranslationArray($weekdays, static::DAYS_PER_WEEK, $timeString)) + : [], + $mode & CarbonInterface::TRANSLATE_DAYS + ? $processList(self::getTranslationArray($messages['weekdays_short'] ?? [], static::DAYS_PER_WEEK, $timeString)) + : [], + $mode & CarbonInterface::TRANSLATE_DIFF ? self::translateWordsByKeys([ 'diff_now', 'diff_today', 'diff_yesterday', @@ -391,7 +316,7 @@ trait Localization 'diff_before_yesterday', 'diff_after_tomorrow', ], $messages, $key) : [], - $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys([ + $mode & CarbonInterface::TRANSLATE_UNITS ? self::translateWordsByKeys([ 'year', 'month', 'week', @@ -400,38 +325,79 @@ trait Localization 'minute', 'second', ], $messages, $key) : [], - $mode & CarbonInterface::TRANSLATE_MERIDIEM ? array_map(function ($hour) use ($meridiem) { + $mode & CarbonInterface::TRANSLATE_MERIDIEM ? $processList(array_map(function ($hour) use ($meridiem) { if (\is_array($meridiem)) { - return $meridiem[$hour < 12 ? 0 : 1]; + return $meridiem[$hour < static::HOURS_PER_DAY / 2 ? 0 : 1]; } return $meridiem($hour, 0, false); - }, range(0, 23)) : [] + }, range(0, 23))) : [], ); } - return substr(preg_replace_callback('/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/iu', function ($match) use ($fromTranslations, $toTranslations) { - [$chunk] = $match; + // Make all dots optional + $fromTranslations = array_map( + static fn (string $word): string => strtr($word, [ + '\\.' => '\\.?', + ]), + $fromTranslations, + ); + $monthNameCount = \count($monthTranslations); + $firstNumberOffset = preg_match('/^\D+\d/', $timeString, $match) + ? \strlen($match[0]) // -1 to remove the first digit, +1 to add the extra wrapping spaced added just below + : INF; + + return substr(preg_replace_callback( + '/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/iu', + function ($match) use ($fromTranslations, $toTranslations, $monthNameCount, $firstNumberOffset) { + /* @var list $match */ + [[$chunk, $offset]] = $match; // @phpstan-ignore offsetAccess.nonArray + + $indexes = self::getMatchingWordIndexes($fromTranslations, $chunk); + + if ($indexes !== []) { + // Before the first number in the string, prefer day names over month names + $bestIndexes = \count($indexes) > 1 && $offset < $firstNumberOffset + ? array_values(array_filter($indexes, static fn ($index) => $index > $monthNameCount)) + : []; + $index = $bestIndexes[0] ?? $indexes[0]; - foreach ($fromTranslations as $index => $word) { - if (preg_match("/^$word\$/iu", $chunk)) { return $toTranslations[$index] ?? ''; } - } - return $chunk; // @codeCoverageIgnore - }, " $timeString "), 1, -1); + return $chunk; // @codeCoverageIgnore + }, + " $timeString ", + flags: PREG_OFFSET_CAPTURE, + ), 1, -1); + } + + private static function getMatchingWordIndexes(array $fromTranslations, string $search): array + { + $indexes = array_keys($fromTranslations, $search, true); + + if ($indexes !== []) { + return $indexes; + } + + foreach ($fromTranslations as $index => $word) { + if (preg_match("/^$word\$/iu", $search)) { + $indexes[] = $index; + } + } + + return $indexes; } /** - * Translate a time string from the current locale (`$date->locale()`) to an other. + * Translate a time string from the current locale (`$date->locale()`) to another one. * * @param string $timeString time string to translate * @param string|null $to output locale of the result returned ("en" by default) * * @return string */ - public function translateTimeStringTo($timeString, $to = null) + public function translateTimeStringTo(string $timeString, ?string $to = null): string { return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to); } @@ -442,9 +408,9 @@ trait Localization * @param string|null $locale * @param string ...$fallbackLocales * - * @return $this|string + * @return ($locale is null ? string : static) */ - public function locale(string $locale = null, ...$fallbackLocales) + public function locale(?string $locale = null, string ...$fallbackLocales): static|string { if ($locale === null) { return $this->getTranslatorLocale(); @@ -476,22 +442,20 @@ trait Localization * * @return string */ - public static function getLocale() + public static function getLocale(): string { return static::getLocaleAwareTranslator()->getLocale(); } /** * Set the current translator locale and indicate if the source locale file exists. - * Pass 'auto' as locale to use closest language from the current LC_TIME locale. + * Pass 'auto' as locale to use the closest language to the current LC_TIME locale. * * @param string $locale locale ex. en - * - * @return bool */ - public static function setLocale($locale) + public static function setLocale(string $locale): void { - return static::getLocaleAwareTranslator()->setLocale($locale) !== false; + static::getLocaleAwareTranslator()->setLocale($locale); } /** @@ -501,19 +465,42 @@ trait Localization * * @param string $locale */ - public static function setFallbackLocale($locale) + public static function setFallbackLocale(string $locale): void { $translator = static::getTranslator(); if (method_exists($translator, 'setFallbackLocales')) { - $translator->setFallbackLocales([$locale]); + $fallbackLocales = [$locale]; + + if ( + method_exists($translator, 'getFallbackLocales') + && $fallbackLocales === $translator->getFallbackLocales() + ) { + return; + } + + $translator->setFallbackLocales($fallbackLocales); if ($translator instanceof Translator) { $preferredLocale = $translator->getLocale(); + $fallbackMessages = []; + $preferredMessages = $translator->getMessages($preferredLocale); + + foreach (Translator::get($locale)->getMessages()[$locale] ?? [] as $key => $value) { + if ( + preg_match('/^(?:a_)?(.+)_(?:standalone|ago|from_now|before|after|short|min)$/', $key, $match) + && isset($preferredMessages[$match[1]]) + ) { + continue; + } + + $fallbackMessages[$key] = $value; + } + $translator->setMessages($preferredLocale, array_replace_recursive( $translator->getMessages()[$locale] ?? [], - Translator::get($locale)->getMessages()[$locale] ?? [], - $translator->getMessages($preferredLocale) + $fallbackMessages, + $preferredMessages, )); } } @@ -523,10 +510,8 @@ trait Localization * Get the fallback locale. * * @see https://symfony.com/doc/current/components/translation.html#fallback-locales - * - * @return string|null */ - public static function getFallbackLocale() + public static function getFallbackLocale(): ?string { $translator = static::getTranslator(); @@ -546,10 +531,17 @@ trait Localization * * @return mixed */ - public static function executeWithLocale($locale, $func) + public static function executeWithLocale(string $locale, callable $func): mixed { $currentLocale = static::getLocale(); - $result = $func(static::setLocale($locale) ? static::getLocale() : false, static::translator()); + static::setLocale($locale); + $newLocale = static::getLocale(); + $result = $func( + $newLocale === 'en' && strtolower(substr($locale, 0, 2)) !== 'en' + ? false + : $newLocale, + static::getTranslator(), + ); static::setLocale($currentLocale); return $result; @@ -563,15 +555,15 @@ trait Localization * * @return bool */ - public static function localeHasShortUnits($locale) + public static function localeHasShortUnits(string $locale): bool { return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { - return ($newLocale && (($y = static::translateWith($translator, 'y')) !== 'y' && $y !== static::translateWith($translator, 'year'))) || ( - ($y = static::translateWith($translator, 'd')) !== 'd' && - $y !== static::translateWith($translator, 'day') + return ($newLocale && (($year = static::translateWith($translator, 'y')) !== 'y' && $year !== static::translateWith($translator, 'year'))) || ( + ($year = static::translateWith($translator, 'd')) !== 'd' && + $year !== static::translateWith($translator, 'day') ) || ( - ($y = static::translateWith($translator, 'h')) !== 'h' && - $y !== static::translateWith($translator, 'hour') + ($year = static::translateWith($translator, 'h')) !== 'h' && + $year !== static::translateWith($translator, 'hour') ); }); } @@ -584,7 +576,7 @@ trait Localization * * @return bool */ - public static function localeHasDiffSyntax($locale) + public static function localeHasDiffSyntax(string $locale): bool { return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { if (!$newLocale) { @@ -615,7 +607,7 @@ trait Localization * * @return bool */ - public static function localeHasDiffOneDayWords($locale) + public static function localeHasDiffOneDayWords(string $locale): bool { return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { return $newLocale && @@ -633,7 +625,7 @@ trait Localization * * @return bool */ - public static function localeHasDiffTwoDayWords($locale) + public static function localeHasDiffTwoDayWords(string $locale): bool { return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { return $newLocale && @@ -667,7 +659,7 @@ trait Localization * * @return array */ - public static function getAvailableLocales() + public static function getAvailableLocales(): array { $translator = static::getLocaleAwareTranslator(); @@ -682,9 +674,10 @@ trait Localization * * @return Language[] */ - public static function getAvailableLocalesInfo() + public static function getAvailableLocalesInfo(): array { $languages = []; + foreach (static::getAvailableLocales() as $id) { $languages[$id] = new Language($id); } @@ -692,29 +685,11 @@ trait Localization return $languages; } - /** - * Initialize the default translator instance if necessary. - * - * @return \Symfony\Component\Translation\TranslatorInterface - */ - protected static function translator() - { - if (static::$translator === null) { - static::$translator = Translator::get(); - } - - return static::$translator; - } - /** * Get the locale of a given translator. * * If null or omitted, current local translator is used. * If no local translator is in use, current global translator is used. - * - * @param null $translator - * - * @return string|null */ protected function getTranslatorLocale($translator = null): ?string { @@ -724,7 +699,7 @@ trait Localization $translator = static::getLocaleAwareTranslator($translator); - return $translator ? $translator->getLocale() : null; + return $translator?->getLocale(); } /** @@ -737,7 +712,7 @@ trait Localization protected static function getLocaleAwareTranslator($translator = null) { if (\func_num_args() === 0) { - $translator = static::translator(); + $translator = static::getTranslator(); } if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) { @@ -756,8 +731,8 @@ trait Localization private static function getFromCatalogue($translator, $catalogue, string $id, string $domain = 'messages') { return $translator instanceof TranslatorStrongTypeInterface - ? $translator->getFromCatalogue($catalogue, $id, $domain) // @codeCoverageIgnore - : $catalogue->get($id, $domain); + ? $translator->getFromCatalogue($catalogue, $id, $domain) + : $catalogue->get($id, $domain); // @codeCoverageIgnore } /** @@ -771,7 +746,11 @@ trait Localization { $word = str_replace([':count', '%count', ':time'], '', $word); $word = strtr($word, ['’' => "'"]); - $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); + $word = preg_replace( + '/\{(?:-?\d+(?:\.\d+)?|-?Inf)(?:,(?:-?\d+|-?Inf))?}|[\[\]](?:-?\d+(?:\.\d+)?|-?Inf)(?:,(?:-?\d+|-?Inf))?[\[\]]/', + '', + $word, + ); return trim($word); } @@ -787,7 +766,7 @@ trait Localization */ private static function translateWordsByKeys($keys, $messages, $key): array { - return array_map(function ($wordKey) use ($messages, $key) { + return array_map(static function ($wordKey) use ($messages, $key) { $message = $key === 'from' && isset($messages[$wordKey.'_regexp']) ? $messages[$wordKey.'_regexp'] : ($messages[$wordKey] ?? null); @@ -798,9 +777,9 @@ trait Localization $parts = explode('|', $message); - return $key === 'to' + return $key === 'to' || \count($parts) === 1 ? self::cleanWordFromTranslationString(end($parts)) - : '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')'; + : '(?:'.implode('|', array_map(static::cleanWordFromTranslationString(...), $parts)).')'; }, $keys); } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php index 92b6c9d86..978c1994b 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Macro.php @@ -1,5 +1,7 @@ hours(11)->userFormat(); * ``` * - * @param string $name - * @param object|callable $macro - * - * @return void + * @param-closure-this static $macro */ - public static function macro($name, $macro) + public static function macro(string $name, ?callable $macro): void { - static::$globalMacros[$name] = $macro; + FactoryImmutable::getDefaultInstance()->macro($name, $macro); } /** * Remove all macros and generic macros. */ - public static function resetMacros() + public static function resetMacros(): void { - static::$globalMacros = []; - static::$globalGenericMacros = []; + FactoryImmutable::getDefaultInstance()->resetMacros(); } /** * Register a custom macro. * - * @param object|callable $macro - * @param int $priority marco with higher priority is tried first + * @param callable $macro + * @param int $priority marco with higher priority is tried first * * @return void */ - public static function genericMacro($macro, $priority = 0) + public static function genericMacro(callable $macro, int $priority = 0): void { - if (!isset(static::$globalGenericMacros[$priority])) { - static::$globalGenericMacros[$priority] = []; - krsort(static::$globalGenericMacros, SORT_NUMERIC); - } - - static::$globalGenericMacros[$priority][] = $macro; + FactoryImmutable::getDefaultInstance()->genericMacro($macro, $priority); } /** @@ -93,44 +76,36 @@ trait Macro * * @return bool */ - public static function hasMacro($name) + public static function hasMacro(string $name): bool { - return isset(static::$globalMacros[$name]); + return FactoryImmutable::getInstance()->hasMacro($name); } /** * Get the raw callable macro registered globally for a given name. - * - * @param string $name - * - * @return callable|null */ - public static function getMacro($name) + public static function getMacro(string $name): ?callable { - return static::$globalMacros[$name] ?? null; + return FactoryImmutable::getInstance()->getMacro($name); } /** * Checks if macro is registered globally or locally. - * - * @param string $name - * - * @return bool */ - public function hasLocalMacro($name) + public function hasLocalMacro(string $name): bool { - return ($this->localMacros && isset($this->localMacros[$name])) || static::hasMacro($name); + return ($this->localMacros && isset($this->localMacros[$name])) || $this->transmitFactory( + static fn () => static::hasMacro($name), + ); } /** * Get the raw callable macro registered globally or locally for a given name. - * - * @param string $name - * - * @return callable|null */ - public function getLocalMacro($name) + public function getLocalMacro(string $name): ?callable { - return ($this->localMacros ?? [])[$name] ?? static::getMacro($name); + return ($this->localMacros ?? [])[$name] ?? $this->transmitFactory( + static fn () => static::getMacro($name), + ); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php index 310a44d0c..d6595f186 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/MagicParameter.php @@ -1,5 +1,7 @@ getMethods( - ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED, ); foreach ($methods as $method) { - if ($method->isConstructor() || $method->isDestructor()) { + if (self::cannotBeAMixinMethod($method)) { continue; } - $method->setAccessible(true); + $macro = $method->invoke($method->isStatic() ? null : $mixin); - static::macro($method->name, $method->invoke($mixin)); + if (\is_callable($macro)) { + static::macro($method->name, $macro); + } } } - /** - * @param string $trait - */ - private static function loadMixinTrait($trait) + private static function cannotBeAMixinMethod(ReflectionMethod $method): bool + { + if ($method->isConstructor() || $method->isDestructor()) { + return true; + } + + $returnType = $method->getReturnType(); + + if ($returnType instanceof ReflectionNamedType) { + $returnedTypeName = $returnType->getName(); + + if ($returnType->isBuiltin()) { + return !\in_array($returnedTypeName, [ + 'callable', + 'object', // could have __invoke + 'array', // could be [MyClass::class, 'myMethod'] + 'mixed', // could be one of the above + // The other builtin types cannot be callable, so we can skip invoking them + ], true); + } + + // If it returns a non-invokable object, it cannot be a mixin method + if (class_exists($returnedTypeName)) { + return !is_a($returnedTypeName, Closure::class, true) && !\is_callable([$returnedTypeName, '__invoke']); + } + } + + return false; + } + + private static function loadMixinTrait(string $trait): void { $context = eval(self::getAnonymousClassCodeForTrait($trait)); $className = \get_class($context); @@ -114,7 +138,7 @@ trait Mixin try { // @ is required to handle error if not converted into exceptions $closure = @$closureBase->bindTo($context); - } catch (Throwable $throwable) { // @codeCoverageIgnore + } catch (Throwable) { // @codeCoverageIgnore $closure = $closureBase; // @codeCoverageIgnore } @@ -167,7 +191,7 @@ trait Mixin } } - private static function getAnonymousClassCodeForTrait(string $trait) + private static function getAnonymousClassCodeForTrait(string $trait): string { return 'return new class() extends '.static::class.' {use '.$trait.';};'; } @@ -185,15 +209,8 @@ trait Mixin /** * Stack a Carbon context from inside calls of self::this() and execute a given action. - * - * @param static|null $context - * @param callable $callable - * - * @throws Throwable - * - * @return mixed */ - protected static function bindMacroContext($context, callable $callable) + protected static function bindMacroContext(?self $context, callable $callable): mixed { static::$macroContextStack[] = $context; @@ -206,20 +223,16 @@ trait Mixin /** * Return the current context from inside a macro callee or a null if static. - * - * @return static|null */ - protected static function context() + protected static function context(): ?static { return end(static::$macroContextStack) ?: null; } /** * Return the current context from inside a macro callee or a new one if static. - * - * @return static */ - protected static function this() + protected static function this(): static { return end(static::$macroContextStack) ?: new static(); } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php index 39343d8fa..7fc0f9f6c 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Modifiers.php @@ -1,5 +1,7 @@ change( - 'next '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + 'next '.(\is_string($modifier) ? $modifier : static::$days[$modifier]), ); } @@ -157,7 +160,7 @@ trait Modifiers * * @param string|int|null $modifier * - * @return static|false + * @return static */ public function previous($modifier = null) { @@ -166,7 +169,7 @@ trait Modifiers } return $this->change( - 'last '.(\is_string($modifier) ? $modifier : static::$days[$modifier]) + 'last '.(\is_string($modifier) ? $modifier : static::$days[$modifier]), ); } @@ -341,7 +344,7 @@ trait Modifiers */ public function average($date = null) { - return $this->addRealMicroseconds((int) ($this->diffInRealMicroseconds($this->resolveCarbon($date), false) / 2)); + return $this->addRealMicroseconds((int) ($this->diffInMicroseconds($this->resolveCarbon($date), false) / 2)); } /** @@ -354,7 +357,7 @@ trait Modifiers */ public function closest($date1, $date2) { - return $this->diffInRealMicroseconds($date1) < $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + return $this->diffInMicroseconds($date1, true) < $this->diffInMicroseconds($date2, true) ? $date1 : $date2; } /** @@ -367,7 +370,7 @@ trait Modifiers */ public function farthest($date1, $date2) { - return $this->diffInRealMicroseconds($date1) > $this->diffInRealMicroseconds($date2) ? $date1 : $date2; + return $this->diffInMicroseconds($date1, true) > $this->diffInMicroseconds($date2, true) ? $date1 : $date2; } /** @@ -431,12 +434,13 @@ trait Modifiers * * @see https://php.net/manual/en/datetime.modify.php * - * @return static|false + * @return static */ #[ReturnTypeWillChange] public function modify($modify) { - return parent::modify((string) $modify); + return parent::modify((string) $modify) + ?: throw new InvalidFormatException('Could not modify with: '.var_export($modify, true)); } /** @@ -451,7 +455,7 @@ trait Modifiers * * @param string $modifier * - * @return static|false + * @return static */ public function change($modifier) { diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php index 561c867d0..d2be009f6 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Mutability.php @@ -1,5 +1,7 @@ cast(Carbon::class); - - return $date; + return $this->cast(Carbon::class); } /** - * Return a immutable copy of the instance. - * - * @return CarbonImmutable + * Return an immutable copy of the instance. */ - public function toImmutable() + public function toImmutable(): CarbonImmutable { - /** @var CarbonImmutable $date */ - $date = $this->cast(CarbonImmutable::class); + // Immutable objects are fine as is (uncloned) + if ($this::class === CarbonImmutable::class) { + return $this; + } - return $date; + return $this->cast(CarbonImmutable::class); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php index c77a10244..4b6e3d035 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ObjectInitialisation.php @@ -1,5 +1,7 @@ - */ - protected static $regexFormats = [ - 'd' => '(3[01]|[12][0-9]|0[1-9])', - 'D' => '(Sun|Mon|Tue|Wed|Thu|Fri|Sat)', - 'j' => '([123][0-9]|[1-9])', - 'l' => '([a-zA-Z]{2,})', - 'N' => '([1-7])', - 'S' => '(st|nd|rd|th)', - 'w' => '([0-6])', - 'z' => '(36[0-5]|3[0-5][0-9]|[12][0-9]{2}|[1-9]?[0-9])', - 'W' => '(5[012]|[1-4][0-9]|0?[1-9])', - 'F' => '([a-zA-Z]{2,})', - 'm' => '(1[012]|0[1-9])', - 'M' => '([a-zA-Z]{3})', - 'n' => '(1[012]|[1-9])', - 't' => '(2[89]|3[01])', - 'L' => '(0|1)', - 'o' => '([1-9][0-9]{0,4})', - 'Y' => '([1-9]?[0-9]{4})', - 'y' => '([0-9]{2})', - 'a' => '(am|pm)', - 'A' => '(AM|PM)', - 'B' => '([0-9]{3})', - 'g' => '(1[012]|[1-9])', - 'G' => '(2[0-3]|1?[0-9])', - 'h' => '(1[012]|0[1-9])', - 'H' => '(2[0-3]|[01][0-9])', - 'i' => '([0-5][0-9])', - 's' => '([0-5][0-9])', - 'u' => '([0-9]{1,6})', - 'v' => '([0-9]{1,3})', - 'e' => '([a-zA-Z]{1,5})|([a-zA-Z]*\\/[a-zA-Z]*)', - 'I' => '(0|1)', - 'O' => '([+-](1[012]|0[0-9])[0134][05])', - 'P' => '([+-](1[012]|0[0-9]):[0134][05])', - 'p' => '(Z|[+-](1[012]|0[0-9]):[0134][05])', - 'T' => '([a-zA-Z]{1,5})', - 'Z' => '(-?[1-5]?[0-9]{1,4})', - 'U' => '([0-9]*)', - - // The formats below are combinations of the above formats. - 'c' => '(([1-9]?[0-9]{4})-(1[012]|0[1-9])-(3[01]|[12][0-9]|0[1-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])[+-](1[012]|0[0-9]):([0134][05]))', // Y-m-dTH:i:sP - 'r' => '(([a-zA-Z]{3}), ([123][0-9]|0[1-9]) ([a-zA-Z]{3}) ([1-9]?[0-9]{4}) (2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]) [+-](1[012]|0[0-9])([0134][05]))', // D, d M Y H:i:s O - ]; - - /** - * Format modifiers (such as available in createFromFormat) regex patterns. - * - * @var array - */ - protected static $regexFormatModifiers = [ - '*' => '.+', - ' ' => '[ ]', - '#' => '[;:\\/.,()-]', - '?' => '([^a]|[a])', - '!' => '', - '|' => '', - '+' => '', - ]; - - /** - * Indicates if months should be calculated with overflow. - * Global setting. - * - * @var bool - */ - protected static $monthsOverflow = true; - - /** - * Indicates if years should be calculated with overflow. - * Global setting. - * - * @var bool - */ - protected static $yearsOverflow = true; - - /** - * Indicates if the strict mode is in use. - * Global setting. - * - * @var bool - */ - protected static $strictModeEnabled = true; - - /** - * Function to call instead of format. - * - * @var string|callable|null - */ - protected static $formatFunction; - - /** - * Function to call instead of createFromFormat. - * - * @var string|callable|null - */ - protected static $createFromFormatFunction; - - /** - * Function to call instead of parse. - * - * @var string|callable|null - */ - protected static $parseFunction; - /** * Indicates if months should be calculated with overflow. * Specific setting. - * - * @var bool|null */ - protected $localMonthsOverflow; + protected ?bool $localMonthsOverflow = null; /** * Indicates if years should be calculated with overflow. * Specific setting. - * - * @var bool|null */ - protected $localYearsOverflow; + protected ?bool $localYearsOverflow = null; /** * Indicates if the strict mode is in use. * Specific setting. - * - * @var bool|null */ - protected $localStrictModeEnabled; + protected ?bool $localStrictModeEnabled = null; /** * Options for diffForHumans and forHumans methods. - * - * @var bool|null */ - protected $localHumanDiffOptions; + protected ?int $localHumanDiffOptions = null; /** * Format to use on string cast. * - * @var string|null + * @var string|callable|null */ - protected $localToStringFormat; + protected $localToStringFormat = null; /** * Format to use on JSON serialization. * - * @var string|null + * @var string|callable|null */ - protected $localSerializer; + protected $localSerializer = null; /** * Instance-specific macros. - * - * @var array|null */ - protected $localMacros; + protected ?array $localMacros = null; /** * Instance-specific generic macros. - * - * @var array|null */ - protected $localGenericMacros; + protected ?array $localGenericMacros = null; /** * Function to call instead of format. * * @var string|callable|null */ - protected $localFormatFunction; - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * @see settings - * - * Enable the strict mode (or disable with passing false). - * - * @param bool $strictModeEnabled - */ - public static function useStrictMode($strictModeEnabled = true) - { - static::$strictModeEnabled = $strictModeEnabled; - } - - /** - * Returns true if the strict mode is globally in use, false else. - * (It can be overridden in specific instances.) - * - * @return bool - */ - public static function isStrictModeEnabled() - { - return static::$strictModeEnabled; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @see settings - * - * Indicates if months should be calculated with overflow. - * - * @param bool $monthsOverflow - * - * @return void - */ - public static function useMonthsOverflow($monthsOverflow = true) - { - static::$monthsOverflow = $monthsOverflow; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @see settings - * - * Reset the month overflow behavior. - * - * @return void - */ - public static function resetMonthsOverflow() - { - static::$monthsOverflow = true; - } - - /** - * Get the month overflow global behavior (can be overridden in specific instances). - * - * @return bool - */ - public static function shouldOverflowMonths() - { - return static::$monthsOverflow; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @see settings - * - * Indicates if years should be calculated with overflow. - * - * @param bool $yearsOverflow - * - * @return void - */ - public static function useYearsOverflow($yearsOverflow = true) - { - static::$yearsOverflow = $yearsOverflow; - } - - /** - * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. - * You should rather use the ->settings() method. - * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants - * are available for quarters, years, decade, centuries, millennia (singular and plural forms). - * @see settings - * - * Reset the month overflow behavior. - * - * @return void - */ - public static function resetYearsOverflow() - { - static::$yearsOverflow = true; - } - - /** - * Get the month overflow global behavior (can be overridden in specific instances). - * - * @return bool - */ - public static function shouldOverflowYears() - { - return static::$yearsOverflow; - } + protected $localFormatFunction = null; /** * Set specific options. @@ -364,7 +102,7 @@ trait Options * * @return $this|static */ - public function settings(array $settings) + public function settings(array $settings): static { $this->localStrictModeEnabled = $settings['strictMode'] ?? null; $this->localMonthsOverflow = $settings['monthOverflow'] ?? null; @@ -384,6 +122,8 @@ trait Options } $this->locale(...$locales); + } elseif (isset($settings['translator']) && property_exists($this, 'localTranslator')) { + $this->localTranslator = $settings['translator']; } if (isset($settings['innerTimezone'])) { @@ -399,10 +139,8 @@ trait Options /** * Returns current local settings. - * - * @return array */ - public function getSettings() + public function getSettings(): array { $settings = []; $map = [ @@ -432,16 +170,14 @@ trait Options /** * Show truthy properties on var_dump(). - * - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { $infos = array_filter(get_object_vars($this), static function ($var) { return $var; }); - foreach (['dumpProperties', 'constructedObjectId', 'constructed'] as $property) { + foreach (['dumpProperties', 'constructedObjectId', 'constructed', 'originalInput'] as $property) { if (isset($infos[$property])) { unset($infos[$property]); } @@ -449,21 +185,31 @@ trait Options $this->addExtraDebugInfos($infos); + foreach (["\0*\0", ''] as $prefix) { + $key = $prefix.'carbonRecurrences'; + + if (\array_key_exists($key, $infos)) { + $infos['recurrences'] = $infos[$key]; + unset($infos[$key]); + } + } + return $infos; } - protected function addExtraDebugInfos(&$infos): void + protected function isLocalStrictModeEnabled(): bool + { + return $this->localStrictModeEnabled + ?? $this->transmitFactory(static fn () => static::isStrictModeEnabled()); + } + + protected function addExtraDebugInfos(array &$infos): void { if ($this instanceof DateTimeInterface) { try { - if (!isset($infos['date'])) { - $infos['date'] = $this->format(CarbonInterface::MOCK_DATETIME_FORMAT); - } - - if (!isset($infos['timezone'])) { - $infos['timezone'] = $this->tzName; - } - } catch (Throwable $exception) { + $infos['date'] ??= $this->format(CarbonInterface::MOCK_DATETIME_FORMAT); + $infos['timezone'] ??= $this->tzName ?? $this->timezoneSetting ?? $this->timezone ?? null; + } catch (Throwable) { // noop } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php index 85ff5a711..33f4bb112 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Rounding.php @@ -1,5 +1,7 @@ [static::YEARS_PER_MILLENNIUM, 'year'], @@ -69,6 +71,38 @@ trait Rounding $precision *= $factor; + // How many of each unit make up the next one up. Only the steps with a + // fixed size are listed: a month holds a varying number of days, so a + // day precision is never carried into a month. + $unitsPerNextUnit = [ + // @call roundUnit + 'microsecond' => [static::MICROSECONDS_PER_SECOND, 'second'], + // @call roundUnit + 'second' => [static::SECONDS_PER_MINUTE, 'minute'], + // @call roundUnit + 'minute' => [static::MINUTES_PER_HOUR, 'hour'], + // @call roundUnit + 'hour' => [static::HOURS_PER_DAY, 'day'], + // @call roundUnit + 'month' => [static::MONTHS_PER_YEAR, 'year'], + ]; + + // A numeric precision counts units of $normalizedUnit, and that unit has + // a range it cannot leave, so a precision spanning the next unit up has + // to be re-targeted there, the same way "week" becomes "day" above. + if (\is_int($precision) || \is_float($precision)) { + while (isset($unitsPerNextUnit[$normalizedUnit])) { + [$span, $nextUnit] = $unitsPerNextUnit[$normalizedUnit]; + + if ($precision < $span) { + break; + } + + $precision /= $span; + $normalizedUnit = $nextUnit; + } + } + if (!isset($ranges[$normalizedUnit])) { throw new UnknownUnitException($unit); } @@ -113,7 +147,7 @@ trait Rounding } $changes[$unit] = round( - $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0) + $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0), ); // Cannot use modulo as it lose double precision @@ -140,63 +174,40 @@ trait Rounding /** * Truncate the current instance at the given unit with given precision if specified. - * - * @param string $unit - * @param float|int $precision - * - * @return CarbonInterface */ - public function floorUnit($unit, $precision = 1) + public function floorUnit(string $unit, DateInterval|Duration|string|float|int $precision = 1): static { return $this->roundUnit($unit, $precision, 'floor'); } /** * Ceil the current instance at the given unit with given precision if specified. - * - * @param string $unit - * @param float|int $precision - * - * @return CarbonInterface */ - public function ceilUnit($unit, $precision = 1) + public function ceilUnit(string $unit, DateInterval|Duration|string|float|int $precision = 1): static { return $this->roundUnit($unit, $precision, 'ceil'); } /** * Round the current instance second with given precision if specified. - * - * @param float|int|string|\DateInterval|null $precision - * @param string $function - * - * @return CarbonInterface */ - public function round($precision = 1, $function = 'round') + public function round(DateInterval|string|float|int $precision = 1, callable|string $function = 'round'): static { return $this->roundWith($precision, $function); } /** * Round the current instance second with given precision if specified. - * - * @param float|int|string|\DateInterval|null $precision - * - * @return CarbonInterface */ - public function floor($precision = 1) + public function floor(DateInterval|string|float|int $precision = 1): static { return $this->round($precision, 'floor'); } /** * Ceil the current instance second with given precision if specified. - * - * @param float|int|string|\DateInterval|null $precision - * - * @return CarbonInterface */ - public function ceil($precision = 1) + public function ceil(DateInterval|string|float|int $precision = 1): static { return $this->round($precision, 'ceil'); } @@ -204,26 +215,22 @@ trait Rounding /** * Round the current instance week. * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week - * - * @return CarbonInterface + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week */ - public function roundWeek($weekStartsAt = null) + public function roundWeek(WeekDay|int|null $weekStartsAt = null): static { return $this->closest( $this->avoidMutation()->floorWeek($weekStartsAt), - $this->avoidMutation()->ceilWeek($weekStartsAt) + $this->avoidMutation()->ceilWeek($weekStartsAt), ); } /** * Truncate the current instance week. * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week - * - * @return CarbonInterface + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week */ - public function floorWeek($weekStartsAt = null) + public function floorWeek(WeekDay|int|null $weekStartsAt = null): static { return $this->startOfWeek($weekStartsAt); } @@ -231,11 +238,9 @@ trait Rounding /** * Ceil the current instance week. * - * @param int $weekStartsAt optional start allow you to specify the day of week to use to start the week - * - * @return CarbonInterface + * @param WeekDay|int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week */ - public function ceilWeek($weekStartsAt = null) + public function ceilWeek(WeekDay|int|null $weekStartsAt = null): static { if ($this->isMutable()) { $startOfWeek = $this->avoidMutation()->startOfWeek($weekStartsAt); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php index c1d5c5e13..0a129f999 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Serialization.php @@ -1,5 +1,7 @@ [Carbon::class, CarbonImmutable::class]]); + * ``` + * + * @param \Stringable|string $value + * @param array $options example: ['allowed_classes' => [CarbonImmutable::class]] * * @throws InvalidFormatException * * @return static */ - public static function fromSerialized($value) + public static function fromSerialized($value, array $options = []): static { - $instance = @unserialize((string) $value); + $instance = @unserialize((string) $value, $options); if (!$instance instanceof static) { throw new InvalidFormatException("Invalid serialized value: $value"); @@ -103,7 +118,7 @@ trait Serialization * @return static */ #[ReturnTypeWillChange] - public static function __set_state($dump) + public static function __set_state($dump): static { if (\is_string($dump)) { return static::parse($dump); @@ -117,30 +132,9 @@ trait Serialization return static::instance($date); } - /** - * Returns the list of properties to dump on serialize() called on. - * - * Only used by PHP < 7.4. - * - * @return array - */ - public function __sleep() - { - $properties = $this->getSleepProperties(); - - if ($this->localTranslator ?? null) { - $properties[] = 'dumpLocale'; - $this->dumpLocale = $this->locale ?? null; - } - - return $properties; - } - /** * Returns the values to dump on serialize() called on. * - * Only used by PHP >= 7.4. - * * @return array */ public function __serialize(): array @@ -148,9 +142,9 @@ trait Serialization // @codeCoverageIgnoreStart if (isset($this->timezone_type, $this->timezone, $this->date)) { return [ - 'date' => $this->date ?? null, + 'date' => $this->date, 'timezone_type' => $this->timezone_type, - 'timezone' => $this->timezone ?? null, + 'timezone' => $this->dumpTimezone($this->timezone), ]; } // @codeCoverageIgnoreEnd @@ -164,9 +158,10 @@ trait Serialization // @codeCoverageIgnoreStart if (\extension_loaded('msgpack') && isset($this->constructedObjectId)) { + $timezone = $this->timezone ?? null; $export['dumpDateProperties'] = [ 'date' => $this->format('Y-m-d H:i:s.u'), - 'timezone' => serialize($this->timezone ?? null), + 'timezone' => $this->dumpTimezone($timezone), ]; } // @codeCoverageIgnoreEnd @@ -180,46 +175,6 @@ trait Serialization /** * Set locale if specified on unserialize() called. - * - * Only used by PHP < 7.4. - * - * @return void - */ - #[ReturnTypeWillChange] - public function __wakeup() - { - if (parent::class && method_exists(parent::class, '__wakeup')) { - // @codeCoverageIgnoreStart - try { - parent::__wakeup(); - } catch (Throwable $exception) { - try { - // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. - ['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties; - parent::__construct($date, unserialize($timezone)); - } catch (Throwable $ignoredException) { - throw $exception; - } - } - // @codeCoverageIgnoreEnd - } - - $this->constructedObjectId = spl_object_hash($this); - - if (isset($this->dumpLocale)) { - $this->locale($this->dumpLocale); - $this->dumpLocale = null; - } - - $this->cleanupDumpProperties(); - } - - /** - * Set locale if specified on unserialize() called. - * - * Only used by PHP >= 7.4. - * - * @return void */ public function __unserialize(array $data): void { @@ -234,8 +189,8 @@ trait Serialization try { // FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later. ['date' => $date, 'timezone' => $timezone] = $data['dumpDateProperties']; - $this->__construct($date, unserialize($timezone)); - } catch (Throwable $ignoredException) { + $this->__construct($date, $timezone); + } catch (Throwable) { throw $exception; } } @@ -248,13 +203,12 @@ trait Serialization /** * Prepare the object for JSON serialization. - * - * @return array|string */ - #[ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): mixed { - $serializer = $this->localSerializer ?? static::$serializer; + $serializer = $this->localSerializer + ?? $this->getFactory()->getSettings()['toJsonFormat'] + ?? null; if ($serializer) { return \is_string($serializer) @@ -270,14 +224,10 @@ trait Serialization * You should rather transform Carbon object before the serialization. * * JSON serialize all Carbon instances using the given callback. - * - * @param callable $callback - * - * @return void */ - public static function serializeUsing($callback) + public static function serializeUsing(string|callable|null $format): void { - static::$serializer = $callback; + FactoryImmutable::getDefaultInstance()->serializeUsing($format); } /** @@ -287,7 +237,7 @@ trait Serialization * var_export($date) * get_object_vars($date) */ - public function cleanupDumpProperties() + public function cleanupDumpProperties(): self { // @codeCoverageIgnoreStart if (PHP_VERSION < 8.2) { @@ -302,25 +252,9 @@ trait Serialization return $this; } - private function getSleepProperties(): array + /** @codeCoverageIgnore */ + private function dumpTimezone(mixed $timezone): mixed { - $properties = $this->dumpProperties; - - // @codeCoverageIgnoreStart - if (!\extension_loaded('msgpack')) { - return $properties; - } - - if (isset($this->constructedObjectId)) { - $this->dumpDateProperties = [ - 'date' => $this->format('Y-m-d H:i:s.u'), - 'timezone' => serialize($this->timezone ?? null), - ]; - - $properties[] = 'dumpDateProperties'; - } - - return $properties; - // @codeCoverageIgnoreEnd + return $timezone instanceof DateTimeZone ? $timezone->getName() : $timezone; } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/StaticLocalization.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/StaticLocalization.php new file mode 100644 index 000000000..cb1e9e601 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/StaticLocalization.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\FactoryImmutable; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Static config for localization. + */ +trait StaticLocalization +{ + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + */ + public static function setHumanDiffOptions(int $humanDiffOptions): void + { + FactoryImmutable::getDefaultInstance()->setHumanDiffOptions($humanDiffOptions); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + */ + public static function enableHumanDiffOption(int $humanDiffOption): void + { + FactoryImmutable::getDefaultInstance()->enableHumanDiffOption($humanDiffOption); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + */ + public static function disableHumanDiffOption(int $humanDiffOption): void + { + FactoryImmutable::getDefaultInstance()->disableHumanDiffOption($humanDiffOption); + } + + /** + * Return default humanDiff() options (merged flags as integer). + */ + public static function getHumanDiffOptions(): int + { + return FactoryImmutable::getInstance()->getHumanDiffOptions(); + } + + /** + * Set the default translator instance to use. + * + * @param TranslatorInterface $translator + * + * @return void + */ + public static function setTranslator(TranslatorInterface $translator): void + { + FactoryImmutable::getDefaultInstance()->setTranslator($translator); + } + + /** + * Initialize the default translator instance if necessary. + */ + public static function getTranslator(): TranslatorInterface + { + return FactoryImmutable::getInstance()->getTranslator(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/StaticOptions.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/StaticOptions.php new file mode 100644 index 000000000..44dd284e1 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/StaticOptions.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +use Carbon\FactoryImmutable; + +/** + * Options related to a static variable. + */ +trait StaticOptions +{ + /////////////////////////////////////////////////////////////////// + ///////////// Behavior customization for sub-classes ////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Function to call instead of format. + * + * @var string|callable|null + */ + protected static $formatFunction; + + /** + * Function to call instead of createFromFormat. + * + * @var string|callable|null + */ + protected static $createFromFormatFunction; + + /** + * Function to call instead of parse. + * + * @var string|callable|null + */ + protected static $parseFunction; + + /////////////////////////////////////////////////////////////////// + ///////////// Use default factory for static options ////////////// + /////////////////////////////////////////////////////////////////// + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * @see settings + * + * Enable the strict mode (or disable with passing false). + * + * @param bool $strictModeEnabled + */ + public static function useStrictMode(bool $strictModeEnabled = true): void + { + FactoryImmutable::getDefaultInstance()->useStrictMode($strictModeEnabled); + } + + /** + * Returns true if the strict mode is globally in use, false else. + * (It can be overridden in specific instances.) + * + * @return bool + */ + public static function isStrictModeEnabled(): bool + { + return FactoryImmutable::getInstance()->isStrictModeEnabled(); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if months should be calculated with overflow. + * + * @param bool $monthsOverflow + * + * @return void + */ + public static function useMonthsOverflow(bool $monthsOverflow = true): void + { + FactoryImmutable::getDefaultInstance()->useMonthsOverflow($monthsOverflow); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addMonthsWithOverflow/addMonthsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetMonthsOverflow(): void + { + FactoryImmutable::getDefaultInstance()->resetMonthsOverflow(); + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowMonths(): bool + { + return FactoryImmutable::getInstance()->shouldOverflowMonths(); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Indicates if years should be calculated with overflow. + * + * @param bool $yearsOverflow + * + * @return void + */ + public static function useYearsOverflow(bool $yearsOverflow = true): void + { + FactoryImmutable::getDefaultInstance()->useYearsOverflow($yearsOverflow); + } + + /** + * @deprecated To avoid conflict between different third-party libraries, static setters should not be used. + * You should rather use the ->settings() method. + * Or you can use method variants: addYearsWithOverflow/addYearsNoOverflow, same variants + * are available for quarters, years, decade, centuries, millennia (singular and plural forms). + * @see settings + * + * Reset the month overflow behavior. + * + * @return void + */ + public static function resetYearsOverflow(): void + { + FactoryImmutable::getDefaultInstance()->resetYearsOverflow(); + } + + /** + * Get the month overflow global behavior (can be overridden in specific instances). + * + * @return bool + */ + public static function shouldOverflowYears(): bool + { + return FactoryImmutable::getInstance()->shouldOverflowYears(); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php index f23c72e8f..ecf766494 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Test.php @@ -1,5 +1,7 @@ setTestNow($testNow); } /** * Set a Carbon instance (real or mock) to be returned when a "now" - * instance is created. The provided instance will be returned + * instance is created. The provided instance will be returned * specifically under the following conditions: * - A call to the static now() method, ex. Carbon::now() * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) @@ -87,29 +71,9 @@ trait Test * * @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance */ - public static function setTestNowAndTimezone($testNow = null, $tz = null) + public static function setTestNowAndTimezone($testNow = null, $timezone = null): void { - if ($testNow) { - self::$testDefaultTimezone = self::$testDefaultTimezone ?? date_default_timezone_get(); - } - - $useDateInstanceTimezone = $testNow instanceof DateTimeInterface; - - if ($useDateInstanceTimezone) { - self::setDefaultTimezone($testNow->getTimezone()->getName(), $testNow); - } - - static::setTestNow($testNow); - - if (!$useDateInstanceTimezone) { - $now = static::getMockedTestNow(\func_num_args() === 1 ? null : $tz); - $tzName = $now ? $now->tzName : null; - self::setDefaultTimezone($tzName ?? self::$testDefaultTimezone ?? 'UTC', $now); - } - - if (!$testNow) { - self::$testDefaultTimezone = null; - } + FactoryImmutable::getDefaultInstance()->setTestNowAndTimezone($testNow, $timezone); } /** @@ -126,28 +90,20 @@ trait Test * * @return T */ - public static function withTestNow($testNow, $callback) + public static function withTestNow(mixed $testNow, callable $callback): mixed { - static::setTestNow($testNow); - - try { - $result = $callback(); - } finally { - static::setTestNow(); - } - - return $result; + return FactoryImmutable::getDefaultInstance()->withTestNow($testNow, $callback); } /** * Get the Carbon instance (real or mock) to be returned when a "now" * instance is created. * - * @return Closure|static the current instance used for testing + * @return Closure|CarbonInterface|null the current instance used for testing */ - public static function getTestNow() + public static function getTestNow(): Closure|CarbonInterface|null { - return static::$testNow; + return FactoryImmutable::getInstance()->getTestNow(); } /** @@ -156,73 +112,71 @@ trait Test * * @return bool true if there is a test instance, otherwise false */ - public static function hasTestNow() + public static function hasTestNow(): bool { - return static::getTestNow() !== null; + return FactoryImmutable::getInstance()->hasTestNow(); } /** * Get the mocked date passed in setTestNow() and if it's a Closure, execute it. - * - * @param string|\DateTimeZone $tz - * - * @return \Carbon\CarbonImmutable|\Carbon\Carbon|null */ - protected static function getMockedTestNow($tz) + protected static function getMockedTestNow(DateTimeZone|string|int|null $timezone): ?CarbonInterface { - $testNow = static::getTestNow(); + $testNow = FactoryImmutable::getInstance()->handleTestNowClosure(static::getTestNow(), $timezone); - if ($testNow instanceof Closure) { - $realNow = new DateTimeImmutable('now'); - $testNow = $testNow(static::parse( - $realNow->format('Y-m-d H:i:s.u'), - $tz ?: $realNow->getTimezone() - )); + if ($testNow === null) { + return null; } - /* @var \Carbon\CarbonImmutable|\Carbon\Carbon|null $testNow */ - return $testNow instanceof CarbonInterface - ? $testNow->avoidMutation()->tz($tz) - : $testNow; + $testNow = $testNow->avoidMutation(); + + return $timezone ? $testNow->setTimezone($timezone) : $testNow; } - protected static function mockConstructorParameters(&$time, $tz) + private function mockConstructorParameters(&$time, ?CarbonTimeZone $timezone): void { - /** @var \Carbon\CarbonImmutable|\Carbon\Carbon $testInstance */ - $testInstance = clone static::getMockedTestNow($tz); + $clock = $this->clock?->unwrap(); + $now = $clock instanceof Factory + ? $clock->getTestNow() + : $this->nowFromClock($timezone); + $testInstance = $now ?? self::getMockedTestNowClone($timezone); + + if (!$testInstance) { + return; + } + + if ($testInstance instanceof DateTimeInterface) { + $testInstance = $testInstance->setTimezone($timezone ?? date_default_timezone_get()); + } if (static::hasRelativeKeywords($time)) { $testInstance = $testInstance->modify($time); } + $factory = $this->getClock()?->unwrap(); + + if (!($factory instanceof Factory)) { + $factory = FactoryImmutable::getInstance(); + } + + $testInstance = $factory->handleTestNowClosure($testInstance, $timezone); + $time = $testInstance instanceof self ? $testInstance->rawFormat(static::MOCK_DATETIME_FORMAT) : $testInstance->format(static::MOCK_DATETIME_FORMAT); } - private static function setDefaultTimezone($timezone, DateTimeInterface $date = null) + private static function getMockedTestNowClone($timezone): CarbonInterface|self|null { - $previous = null; - $success = false; + $mock = static::getMockedTestNow($timezone); - try { - $success = date_default_timezone_set($timezone); - } catch (Throwable $exception) { - $previous = $exception; - } + return $mock ? clone $mock : null; + } - if (!$success) { - $suggestion = @CarbonTimeZone::create($timezone)->toRegionName($date); + private function nowFromClock(?CarbonTimeZone $timezone): ?DateTimeImmutable + { + $now = $this->clock?->now(); - throw new InvalidArgumentException( - "Timezone ID '$timezone' is invalid". - ($suggestion && $suggestion !== $timezone ? ", did you mean '$suggestion'?" : '.')."\n". - "It must be one of the IDs from DateTimeZone::listIdentifiers(),\n". - 'For the record, hours/minutes offset are relevant only for a particular moment, '. - 'but not as a default timezone.', - 0, - $previous - ); - } + return $now && $timezone ? $now->setTimezone($timezone) : null; } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php index 88a465c93..43adc5b35 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Timestamp.php @@ -1,5 +1,7 @@ setTimezone($tz); + #[ReturnTypeWillChange] + public static function createFromTimestamp( + float|int|string $timestamp, + DateTimeZone|string|int|null $timezone = null, + ): static { + $date = static::createFromTimestampUTC($timestamp); + + return $timezone === null ? $date : $date->setTimezone($timezone); } /** - * Create a Carbon instance from an timestamp keeping the timezone to UTC. + * Create a Carbon instance from a timestamp keeping the timezone to UTC. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $timestamp - * - * @return static */ - public static function createFromTimestampUTC($timestamp) + public static function createFromTimestampUTC(float|int|string $timestamp): static { [$integer, $decimal] = self::getIntegerAndDecimalParts($timestamp); $delta = floor($decimal / static::MICROSECONDS_PER_SECOND); @@ -60,17 +61,17 @@ trait Timestamp * * @return static */ - public static function createFromTimestampMsUTC($timestamp) + public static function createFromTimestampMsUTC($timestamp): static { [$milliseconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp, 3); $sign = $milliseconds < 0 || ($milliseconds === 0.0 && $microseconds < 0) ? -1 : 1; $milliseconds = abs($milliseconds); - $microseconds = $sign * abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND); + $microseconds = $sign * (abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND)); $seconds = $sign * floor($milliseconds / static::MILLISECONDS_PER_SECOND); $delta = floor($microseconds / static::MICROSECONDS_PER_SECOND); - $seconds += $delta; + $seconds = (int) ($seconds + $delta); $microseconds -= $delta * static::MICROSECONDS_PER_SECOND; - $microseconds = str_pad($microseconds, 6, '0', STR_PAD_LEFT); + $microseconds = str_pad((string) (int) $microseconds, 6, '0', STR_PAD_LEFT); return static::rawCreateFromFormat('U u', "$seconds $microseconds"); } @@ -79,30 +80,24 @@ trait Timestamp * Create a Carbon instance from a timestamp in milliseconds. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $timestamp - * @param \DateTimeZone|string|null $tz - * - * @return static */ - public static function createFromTimestampMs($timestamp, $tz = null) - { - return static::createFromTimestampMsUTC($timestamp) - ->setTimezone($tz); + public static function createFromTimestampMs( + float|int|string $timestamp, + DateTimeZone|string|int|null $timezone = null, + ): static { + $date = static::createFromTimestampMsUTC($timestamp); + + return $timezone === null ? $date : $date->setTimezone($timezone); } /** * Set the instance's timestamp. * * Timestamp input can be given as int, float or a string containing one or more numbers. - * - * @param float|int|string $unixTimestamp - * - * @return static */ - public function timestamp($unixTimestamp) + public function timestamp(float|int|string $timestamp): static { - return $this->setTimestamp($unixTimestamp); + return $this->setTimestamp($timestamp); } /** @@ -123,9 +118,11 @@ trait Timestamp * * @return float */ - public function getPreciseTimestamp($precision = 6) + public function getPreciseTimestamp($precision = 6): float { - return round(((float) $this->rawFormat('Uu')) / pow(10, 6 - $precision)); + $microseconds = (float) $this->getTimestamp() * static::MICROSECONDS_PER_SECOND + (float) $this->rawFormat('u'); + + return round($microseconds / pow(10, 6 - $precision)); } /** @@ -133,7 +130,7 @@ trait Timestamp * * @return float */ - public function valueOf() + public function valueOf(): float { return $this->getPreciseTimestamp(3); } @@ -143,7 +140,7 @@ trait Timestamp * * @return int */ - public function getTimestampMs() + public function getTimestampMs(): int { return (int) $this->getPreciseTimestamp(3); } @@ -155,7 +152,7 @@ trait Timestamp * * @return int */ - public function unix() + public function unix(): int { return $this->getTimestamp(); } @@ -172,7 +169,7 @@ trait Timestamp * * @return array 0-index is integer part, 1-index is decimal part digits */ - private static function getIntegerAndDecimalParts($numbers, $decimals = 6) + private static function getIntegerAndDecimalParts($numbers, $decimals = 6): array { if (\is_int($numbers) || \is_float($numbers)) { $numbers = number_format($numbers, $decimals, '.', ''); diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php index a81164f99..5f0b367f0 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/ToStringFormat.php @@ -1,5 +1,7 @@ resetToStringFormat(); } /** @@ -49,8 +45,8 @@ trait ToStringFormat * * @return void */ - public static function setToStringFormat($format) + public static function setToStringFormat(string|Closure|null $format): void { - static::$toStringFormat = $format; + FactoryImmutable::getDefaultInstance()->setToStringFormat($format); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/TogglableDetection.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/TogglableDetection.php new file mode 100644 index 000000000..89fe8f543 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/TogglableDetection.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon\Traits; + +trait TogglableDetection +{ + private static bool $detectionEnabled = true; + + public static function isDetectionEnabled(): bool + { + return self::$detectionEnabled; + } + + public static function allow(callable $action): mixed + { + self::$detectionEnabled = false; + + try { + return $action(); + } finally { + self::$detectionEnabled = true; + } + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php index 5be14ec7e..32e849d55 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Units.php @@ -1,5 +1,7 @@ addUTCUnit($unit, $value); + } + + /** + * Add seconds to the instance using timestamp. Positive $value travels + * forward while negative $value travels into the past. + * + * @param string $unit + * @param int|float|null $value + * + * @return static + */ + public function addUTCUnit(string $unit, $value = 1): static + { + $value ??= 0; + switch ($unit) { - // @call addRealUnit + // @call addUTCUnit case 'micro': - // @call addRealUnit + // @call addUTCUnit case 'microsecond': /* @var CarbonInterface $this */ $diff = $this->microsecond + $value; @@ -51,95 +78,125 @@ trait Units $time += $seconds; $diff -= $seconds * static::MICROSECONDS_PER_SECOND; $microtime = str_pad((string) $diff, 6, '0', STR_PAD_LEFT); - $tz = $this->tz; + $timezone = $this->tz; - return $this->tz('UTC')->modify("@$time.$microtime")->tz($tz); + return $this->tz('UTC')->modify("@$time.$microtime")->setTimezone($timezone); - // @call addRealUnit + // @call addUTCUnit case 'milli': - // @call addRealUnit + // @call addUTCUnit case 'millisecond': - return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND); + return $this->addUTCUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND); - // @call addRealUnit + // @call addUTCUnit case 'second': break; - // @call addRealUnit + // @call addUTCUnit case 'minute': $value *= static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'hour': $value *= static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'day': $value *= static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'week': $value *= static::DAYS_PER_WEEK * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'month': $value *= 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'quarter': $value *= static::MONTHS_PER_QUARTER * 30 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'year': $value *= 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'decade': $value *= static::YEARS_PER_DECADE * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'century': $value *= static::YEARS_PER_CENTURY * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; - // @call addRealUnit + // @call addUTCUnit case 'millennium': $value *= static::YEARS_PER_MILLENNIUM * 365 * static::HOURS_PER_DAY * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE; break; default: - if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) { + if ($this->isLocalStrictModeEnabled()) { throw new UnitException("Invalid unit for real timestamp add/sub: '$unit'"); } return $this; } - /* @var CarbonInterface $this */ - return $this->setTimestamp((int) ($this->getTimestamp() + $value)); + $seconds = (int) $value; + $microseconds = (int) round( + (abs((float) $value) - abs($seconds)) * ($value < 0 ? -1 : 1) * static::MICROSECONDS_PER_SECOND, + ); + $date = $this->setTimestamp($this->getTimestamp() + $seconds); + + return $microseconds ? $date->addUTCUnit('microsecond', $microseconds) : $date; } - public function subRealUnit($unit, $value = 1) + /** + * @deprecated Prefer to use add subUTCUnit() which more accurately defines what it's doing. + * + * Subtract seconds to the instance using timestamp. Positive $value travels + * into the past while negative $value travels forward. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function subRealUnit($unit, $value = 1): static { - return $this->addRealUnit($unit, -$value); + return $this->addUTCUnit($unit, -$value); + } + + /** + * Subtract seconds to the instance using timestamp. Positive $value travels + * into the past while negative $value travels forward. + * + * @param string $unit + * @param int $value + * + * @return static + */ + public function subUTCUnit($unit, $value = 1): static + { + return $this->addUTCUnit($unit, -$value); } /** @@ -149,7 +206,7 @@ trait Units * * @return bool */ - public static function isModifiableUnit($unit) + public static function isModifiableUnit($unit): bool { static $modifiableUnits = [ // @call addUnit @@ -176,7 +233,7 @@ trait Units * * @return static */ - public function rawAdd(DateInterval $interval) + public function rawAdd(DateInterval $interval): static { return parent::add($interval); } @@ -188,55 +245,80 @@ trait Units * @example $date->add(15, 'days') * @example $date->add(CarbonInterval::days(4)) * - * @param string|DateInterval|Closure|CarbonConverterInterface $unit - * @param int $value - * @param bool|null $overflow + * @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface|Duration $unit + * @param Unit|int|float|string $value + * @param OverflowMode|bool|null $overflow + * @param int|null $anchorDay * * @return static */ #[ReturnTypeWillChange] - public function add($unit, $value = 1, $overflow = null) + public function add($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static { + $unit = Unit::toNameIfUnit($unit); + $value = Unit::toNameIfUnit($value); + if (\is_string($unit) && \func_num_args() === 1) { $unit = CarbonInterval::make($unit, [], true); } if ($unit instanceof CarbonConverterInterface) { - return $this->resolveCarbon($unit->convertDate($this, false)); + $unit = Closure::fromCallable([$unit, 'convertDate']); } if ($unit instanceof Closure) { - return $this->resolveCarbon($unit($this, false)); + $inverted = ($value < 0); + $result = $this; + + self::disallowDecimalPart($value); + + for ($i = abs($value); $i > 0; $i--) { + $result = $result->resolveCarbon($unit($result, $inverted)); + } + + if ($this !== $result && $this->isMutable()) { + return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O')); + } + + return $result; } if ($unit instanceof DateInterval) { return parent::add($unit); } + if ($unit instanceof Duration) { + return $this + ->addUnit(Unit::Second, ($unit->negative ? -1 : 1) * $unit->seconds) + ->addUnit(Unit::Microsecond, ($unit->negative ? -0.001 : 0.001) * $unit->nanoseconds); + } + if (is_numeric($unit)) { [$value, $unit] = [$unit, $value]; } - return $this->addUnit($unit, $value, $overflow); + return $this->addUnit((string) $unit, $value, $overflow, $anchorDay); } /** * Add given units to the current instance. - * - * @param string $unit - * @param int $value - * @param bool|null $overflow - * - * @return static */ - public function addUnit($unit, $value = 1, $overflow = null) - { + public function addUnit( + Unit|string $unit, + $value = 1, + OverflowMode|bool|null $overflow = null, + ?int $anchorDay = null, + ): static { + $unit = Unit::toName($unit); + + $overflow = $this->getOverflowMode($overflow, $anchorDay); + $originalArgs = \func_get_args(); $date = $this; if (!is_numeric($value) || !(float) $value) { - return $date->isMutable() ? $date : $date->avoidMutation(); + return $date->isMutable() ? $date : $date->copy(); } $unit = self::singularUnit($unit); @@ -252,13 +334,35 @@ trait Units $value *= $factor; } + if ($overflow === OverflowMode::AnchorDay) { + $anchorDay ??= $date->day; + $overflow = OverflowMode::NoOverflow; + } + if ($unit === 'weekday') { - $weekendDays = static::getWeekendDays(); + $absoluteValue = abs($value); + $sign = $value < 0 ? -1 : 1; + $intAbsoluteValue = (int) $absoluteValue; + $remaining = ((float) $absoluteValue - (float) $intAbsoluteValue); + + if ($remaining !== 0.0) { + if ($intAbsoluteValue !== 0) { + $date = $date->addUnit($unit, $sign * $intAbsoluteValue); + } + + $date = $date->addDays($sign * $remaining); + + while ($date->isWeekend()) { + $date = $date->addDays($sign); + } + + return $date; + } + + $weekendDays = $this->transmitFactory(static::getWeekendDays(...)); if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) { - $absoluteValue = abs($value); - $sign = $value / max(1, $absoluteValue); - $weekDaysCount = 7 - min(6, \count(array_unique($weekendDays))); + $weekDaysCount = static::DAYS_PER_WEEK - min(static::DAYS_PER_WEEK - 1, \count(array_unique($weekendDays))); $weeks = floor($absoluteValue / $weekDaysCount); for ($diff = $absoluteValue % $weekDaysCount; $diff; $diff--) { @@ -278,75 +382,61 @@ trait Units } elseif ($canOverflow = (\in_array($unit, [ 'month', 'year', - ]) && ($overflow === false || ( + ]) && ($overflow === OverflowMode::NoOverflow || ( $overflow === null && - ($ucUnit = ucfirst($unit).'s') && - !($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}()) + !$this->shouldUnitOverflow($unit) )))) { $day = $date->day; } - $value = (int) $value; - if ($unit === 'milli' || $unit === 'millisecond') { $unit = 'microsecond'; $value *= static::MICROSECONDS_PER_MILLISECOND; } - // Work-around for bug https://bugs.php.net/bug.php?id=75642 - if ($unit === 'micro' || $unit === 'microsecond') { - $microseconds = $this->micro + $value; - $second = (int) floor($microseconds / static::MICROSECONDS_PER_SECOND); - $microseconds %= static::MICROSECONDS_PER_SECOND; - if ($microseconds < 0) { - $microseconds += static::MICROSECONDS_PER_SECOND; - } - $date = $date->microseconds($microseconds); - $unit = 'second'; - $value = $second; - } + $previousException = null; try { - $date = $date->modify("$value $unit"); + $date = self::rawAddUnit($date, $unit, $value); - if (isset($timeString)) { - $date = $date->setTimeFromTimeString($timeString); - } elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) { - $date = $date->modify('last day of previous month'); + if ($date !== null) { + if (isset($timeString)) { + $date = $date->setTimeFromTimeString($timeString); + } elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) { + $date = $date->modify('last day of previous month'); + } + + if ($anchorDay !== null) { + $date = $date->setAnchorDay($anchorDay); + } } - } catch (DateMalformedStringException $ignoredException) { // @codeCoverageIgnore - $date = null; // @codeCoverageIgnore + } catch (DateMalformedStringException|InvalidFormatException|UnsupportedUnitException $exception) { + $date = null; + $previousException = $exception; } - if (!$date) { - throw new UnitException('Unable to add unit '.var_export($originalArgs, true)); - } - - return $date; + return $date ?? throw new UnitException( + 'Unable to add unit '.var_export($originalArgs, true), + previous: $previousException, + ); } /** * Subtract given units to the current instance. - * - * @param string $unit - * @param int $value - * @param bool|null $overflow - * - * @return static */ - public function subUnit($unit, $value = 1, $overflow = null) - { - return $this->addUnit($unit, -$value, $overflow); + public function subUnit( + Unit|string $unit, + $value = 1, + OverflowMode|bool|null $overflow = null, + ?int $anchorDay = null, + ): static { + return $this->addUnit($unit, -$value, $overflow, $anchorDay); } /** * Call native PHP DateTime/DateTimeImmutable sub() method. - * - * @param DateInterval $interval - * - * @return static */ - public function rawSub(DateInterval $interval) + public function rawSub(DateInterval $interval): static { return parent::sub($interval); } @@ -358,36 +448,59 @@ trait Units * @example $date->sub(15, 'days') * @example $date->sub(CarbonInterval::days(4)) * - * @param string|DateInterval|Closure|CarbonConverterInterface $unit - * @param int $value - * @param bool|null $overflow + * @param Unit|int|string|DateInterval|Duration|Closure|CarbonConverterInterface $unit + * @param Unit|int|float|string $value + * @param OverflowMode|bool|null $overflow + * @param int|null $anchorDay * * @return static */ #[ReturnTypeWillChange] - public function sub($unit, $value = 1, $overflow = null) + public function sub($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static { + $unit = Unit::toNameIfUnit($unit); + $value = Unit::toNameIfUnit($value); + if (\is_string($unit) && \func_num_args() === 1) { $unit = CarbonInterval::make($unit, [], true); } if ($unit instanceof CarbonConverterInterface) { - return $this->resolveCarbon($unit->convertDate($this, true)); + $unit = Closure::fromCallable([$unit, 'convertDate']); } if ($unit instanceof Closure) { - return $this->resolveCarbon($unit($this, true)); + $inverted = ($value < 0); + $result = $this; + + self::disallowDecimalPart($value); + + for ($i = abs($value); $i > 0; $i--) { + $result = $result->resolveCarbon($unit($result, !$inverted)); + } + + if ($this !== $result && $this->isMutable()) { + return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O')); + } + + return $result; } if ($unit instanceof DateInterval) { return parent::sub($unit); } + if ($unit instanceof Duration) { + return $this + ->addUnit(Unit::Second, ($unit->negative ? 1 : -1) * $unit->seconds) + ->addUnit(Unit::Microsecond, ($unit->negative ? 0.001 : -0.001) * $unit->nanoseconds); + } + if (is_numeric($unit)) { [$value, $unit] = [$unit, $value]; } - return $this->addUnit($unit, -(float) $value, $overflow); + return $this->addUnit((string) $unit, -(float) $value, $overflow, $anchorDay); } /** @@ -395,18 +508,165 @@ trait Units * * @see sub() * - * @param string|DateInterval $unit - * @param int $value - * @param bool|null $overflow + * @param Unit|int|string|DateInterval|Duration $unit + * @param Unit|int|float|string $value + * @param OverflowMode|bool|null $overflow + * @param int|null $anchorDay * * @return static */ - public function subtract($unit, $value = 1, $overflow = null) + public function subtract($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static { if (\is_string($unit) && \func_num_args() === 1) { $unit = CarbonInterval::make($unit, [], true); } - return $this->sub($unit, $value, $overflow); + return $this->sub($unit, $value, $overflow, $anchorDay); + } + + /** + * Add given amount of time to the current date. + * + * @SuppressWarnings(ExcessiveParameterList) + */ + private function doPlus( + int $years = 0, + int $months = 0, + int|float $weeks = 0, + int|float $days = 0, + int|float $hours = 0, + int|float $minutes = 0, + int|float $seconds = 0, + int|float $microseconds = 0, + OverflowMode|bool|null $overflow = null, + ?int $anchorDay = null, + ): static { + return $this->addUnit(Unit::Year, $years, $overflow, $anchorDay) + ->addUnit(Unit::Month, $months, $overflow, $anchorDay) + ->add(" + $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + /** + * Subtract given amount of time to the current date. + * + * @SuppressWarnings(ExcessiveParameterList) + */ + private function doMinus( + int $years = 0, + int $months = 0, + int|float $weeks = 0, + int|float $days = 0, + int|float $hours = 0, + int|float $minutes = 0, + int|float $seconds = 0, + int|float $microseconds = 0, + OverflowMode|bool|null $overflow = null, + ?int $anchorDay = null, + ): static { + return $this->subUnit(Unit::Year, $years, $overflow, $anchorDay) + ->subUnit(Unit::Month, $months, $overflow, $anchorDay) + ->sub(" + $weeks weeks $days days + $hours hours $minutes minutes $seconds seconds $microseconds microseconds + "); + } + + private function callPlusOrMinus(string $method, array $parameters): ?static + { + return match ($method) { + 'plus' => $this->doPlus(...$parameters), + 'minus' => $this->doMinus(...$parameters), + default => null, + }; + } + + private static function rawAddUnit(self $date, string $unit, int|float $value): ?static + { + try { + $absoluteValue = abs($value); + + return $date->rawAdd( + CarbonInterval::fromString(self::getNumberAsString($absoluteValue)." $unit") + ->invert($value < 0), + ); + } catch (InvalidIntervalException $exception) { + try { + return $date->modify(self::getNumberAsString($value)." $unit"); + } catch (InvalidFormatException) { + throw new UnsupportedUnitException($unit, previous: $exception); + } + } + } + + private static function getNumberAsString(int|float $value): string + { + $stringValue = (string) $value; + + if ($value < -1 || $value > 1) { + if (str_contains($stringValue, 'E')) { + return number_format($value, 0, '.', ''); + } + + return $stringValue; + } + + if (str_contains($stringValue, 'E')) { + return number_format($value, 14, '.', ''); + } + + return $stringValue; + } + + /** + * Set current day of the instance to the passed value if it exits in the + * current month, else set current day to the last day of the month. + */ + public function setAnchorDay(int $anchorDay): static + { + if ($anchorDay < 1) { + throw new InvalidArgumentException('$anchorDay must be greater than 0'); + } + + return $this->day(min($anchorDay, $this->daysInMonth)); + } + + private static function disallowDecimalPart(mixed $value): void + { + if (((float) $value) !== ((float) (int) $value)) { + throw new InvalidArgumentException( + 'Interval objects cannot be multiplied by a non-integer value.', + ); + } + } + + private function getOverflowMode( + OverflowMode|bool|null $overflow = null, + ?int $anchorDay = null, + ): ?OverflowMode { + if ($anchorDay !== null) { + $overflow ??= OverflowMode::AnchorDay; + + if ($overflow !== OverflowMode::AnchorDay) { + throw new InvalidArgumentException( + '$anchorDay can be set only $overflow = OverflowMode::AnchorDay', + ); + } + } + + return match ($overflow) { + true => OverflowMode::Overflow, + false => OverflowMode::NoOverflow, + default => $overflow, + }; + } + + private function shouldUnitOverflow(string $unit): bool + { + $ucUnit = ucfirst($unit).'s'; + + return $this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}(); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php index 6f1481456..3f928b528 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Traits/Week.php @@ -1,5 +1,7 @@ weekYear( $year, - $dayOfWeek ?? 1, - $dayOfYear ?? 4 + $dayOfWeek ?? static::MONDAY, + $dayOfYear ?? static::THURSDAY, ); } @@ -69,7 +73,7 @@ trait Week */ public function weekYear($year = null, $dayOfWeek = null, $dayOfYear = null) { - $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? static::SUNDAY; $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; if ($year !== null) { @@ -81,25 +85,24 @@ trait Week $week = $this->week(null, $dayOfWeek, $dayOfYear); $day = $this->dayOfWeek; + $time = $this->avoidMutation(); $date = $this->year($year); - switch ($date->weekYear(null, $dayOfWeek, $dayOfYear) - $year) { - case 1: - $date = $date->subWeeks(26); - break; - case -1: - $date = $date->addWeeks(26); + $date = match ($date->weekYear(null, $dayOfWeek, $dayOfYear) - $year) { + CarbonInterval::POSITIVE => $date->subWeeks(static::WEEKS_PER_YEAR / 2), + CarbonInterval::NEGATIVE => $date->addWeeks(static::WEEKS_PER_YEAR / 2), + default => $date, + }; - break; + $date = $date + ->addWeeks($week - $date->week(null, $dayOfWeek, $dayOfYear)) + ->startOfWeek($dayOfWeek); + + if ($date->dayOfWeek !== $day) { + $date = $date->next($day); } - $date = $date->addWeeks($week - $date->week(null, $dayOfWeek, $dayOfYear))->startOfWeek($dayOfWeek); - - if ($date->dayOfWeek === $day) { - return $date; - } - - return $date->next($day); + return $date->setTimeFrom($time); } $year = $this->year; @@ -132,8 +135,8 @@ trait Week public function isoWeeksInYear($dayOfWeek = null, $dayOfYear = null) { return $this->weeksInYear( - $dayOfWeek ?? 1, - $dayOfYear ?? 4 + $dayOfWeek ?? static::MONDAY, + $dayOfYear ?? static::THURSDAY, ); } @@ -149,7 +152,7 @@ trait Week */ public function weeksInYear($dayOfWeek = null, $dayOfYear = null) { - $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? 0; + $dayOfWeek = $dayOfWeek ?? $this->getTranslationMessage('first_day_of_week') ?? static::SUNDAY; $dayOfYear = $dayOfYear ?? $this->getTranslationMessage('day_of_first_week_of_year') ?? 1; $year = $this->year; $start = $this->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); @@ -163,7 +166,7 @@ trait Week $endDay += $this->daysInYear; } - return (int) round(($endDay - $startDay) / 7); + return (int) round(($endDay - $startDay) / static::DAYS_PER_WEEK); } /** @@ -187,12 +190,14 @@ trait Week return $date->addWeeks(round($week) - $this->week(null, $dayOfWeek, $dayOfYear)); } - $start = $date->avoidMutation()->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); - $end = $date->avoidMutation()->startOfWeek($dayOfWeek); + $start = $date->avoidMutation()->shiftTimezone('UTC')->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $end = $date->avoidMutation()->shiftTimezone('UTC')->startOfWeek($dayOfWeek); + if ($start > $end) { - $start = $start->subWeeks(26)->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); + $start = $start->subWeeks(static::WEEKS_PER_YEAR / 2)->dayOfYear($dayOfYear)->startOfWeek($dayOfWeek); } - $week = (int) ($start->diffInDays($end) / 7 + 1); + + $week = (int) ($start->diffInDays($end) / static::DAYS_PER_WEEK + 1); return $week > $end->weeksInYear($dayOfWeek, $dayOfYear) ? 1 : $week; } @@ -212,8 +217,8 @@ trait Week { return $this->week( $week, - $dayOfWeek ?? 1, - $dayOfYear ?? 4 + $dayOfWeek ?? static::MONDAY, + $dayOfYear ?? static::THURSDAY, ); } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php index 491c9e720..9f523b264 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/Translator.php @@ -1,5 +1,7 @@ hasReturnType() diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php index ce6b2f90a..2d7ecf236 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorImmutable.php @@ -1,5 +1,7 @@ constructed = true; @@ -29,24 +30,24 @@ class TranslatorImmutable extends Translator /** * @codeCoverageIgnore */ - public function setDirectories(array $directories) + public function setDirectories(array $directories): static { $this->disallowMutation(__METHOD__); return parent::setDirectories($directories); } - public function setLocale($locale) + public function setLocale($locale): void { $this->disallowMutation(__METHOD__); - return parent::setLocale($locale); + parent::setLocale($locale); } /** * @codeCoverageIgnore */ - public function setMessages($locale, $messages) + public function setMessages(string $locale, array $messages): static { $this->disallowMutation(__METHOD__); @@ -56,7 +57,7 @@ class TranslatorImmutable extends Translator /** * @codeCoverageIgnore */ - public function setTranslations($messages) + public function setTranslations(array $messages): static { $this->disallowMutation(__METHOD__); @@ -73,7 +74,7 @@ class TranslatorImmutable extends Translator parent::setConfigCacheFactory($configCacheFactory); } - public function resetMessages($locale = null) + public function resetMessages(?string $locale = null): bool { $this->disallowMutation(__METHOD__); @@ -83,7 +84,7 @@ class TranslatorImmutable extends Translator /** * @codeCoverageIgnore */ - public function setFallbackLocales(array $locales) + public function setFallbackLocales(array $locales): void { $this->disallowMutation(__METHOD__); @@ -93,7 +94,7 @@ class TranslatorImmutable extends Translator private function disallowMutation($method) { if ($this->constructed) { - throw new ImmutableException($method.' not allowed on '.static::class); + throw ImmutableException::fromMethod(static::class, $method); } } } diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php index ef4dee8e2..54a798093 100644 --- a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/TranslatorStrongTypeInterface.php @@ -1,5 +1,7 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +enum Unit: string +{ + case Microsecond = 'microsecond'; + case Millisecond = 'millisecond'; + case Second = 'second'; + case Minute = 'minute'; + case Hour = 'hour'; + case Day = 'day'; + case Week = 'week'; + case Month = 'month'; + case Quarter = 'quarter'; + case Year = 'year'; + case Decade = 'decade'; + case Century = 'century'; + case Millennium = 'millennium'; + + public static function toName(self|string $unit): string + { + return $unit instanceof self ? $unit->value : $unit; + } + + /** @internal */ + public static function toNameIfUnit(mixed $unit): mixed + { + return $unit instanceof self ? $unit->value : $unit; + } + + public static function fromName(string $name, ?string $locale = null): self + { + if ($locale !== null) { + $messages = Translator::get($locale)->getMessages($locale) ?? []; + + if ($messages !== []) { + $lowerName = mb_strtolower($name); + + foreach (self::cases() as $unit) { + foreach (['', '_from_now', '_ago', '_after', '_before'] as $suffix) { + $message = $messages[$unit->value.$suffix] ?? null; + + if (\is_string($message)) { + $words = explode('|', mb_strtolower(preg_replace( + '/[{\[\]].+?[}\[\]]/', + '', + str_replace(':count', '', $message), + ))); + + foreach ($words as $word) { + if (trim($word) === $lowerName) { + return $unit; + } + } + } + } + } + } + } + + return self::from(CarbonImmutable::singularUnit($name)); + } + + public function singular(?string $locale = null): string + { + if ($locale !== null) { + return trim(Translator::get($locale)->trans($this->value, [ + '%count%' => 1, + ':count' => 1, + ]), "1 \n\r\t\v\0"); + } + + return $this->value; + } + + public function plural(?string $locale = null): string + { + if ($locale !== null) { + return trim(Translator::get($locale)->trans($this->value, [ + '%count%' => 9, + ':count' => 9, + ]), "9 \n\r\t\v\0"); + } + + return CarbonImmutable::pluralUnit($this->value); + } + + public function interval(int|float $value = 1): CarbonInterval + { + return CarbonInterval::fromString("$value $this->name"); + } + + public function locale(string $locale): CarbonInterval + { + return $this->interval()->locale($locale); + } + + public function toPeriod(...$params): CarbonPeriod + { + return $this->interval()->toPeriod(...$params); + } + + public function stepBy(mixed $interval, Unit|string|null $unit = null): CarbonPeriod + { + return $this->interval()->stepBy($interval, $unit); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/WeekDay.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/WeekDay.php new file mode 100644 index 000000000..69f69cebc --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/WeekDay.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use Carbon\Exceptions\InvalidFormatException; + +enum WeekDay: int +{ + // Using constants is only safe starting from PHP 8.2 + case Sunday = 0; // CarbonInterface::SUNDAY + case Monday = 1; // CarbonInterface::MONDAY + case Tuesday = 2; // CarbonInterface::TUESDAY + case Wednesday = 3; // CarbonInterface::WEDNESDAY + case Thursday = 4; // CarbonInterface::THURSDAY + case Friday = 5; // CarbonInterface::FRIDAY + case Saturday = 6; // CarbonInterface::SATURDAY + + public static function int(self|int|null $value): ?int + { + return $value instanceof self ? $value->value : $value; + } + + public static function fromNumber(int $number): self + { + $day = $number % CarbonInterface::DAYS_PER_WEEK; + + return self::from($day + ($day < 0 ? CarbonInterface::DAYS_PER_WEEK : 0)); + } + + public static function fromName(string $name, ?string $locale = null): self + { + try { + return self::from(CarbonImmutable::parseFromLocale($name, $locale)->dayOfWeek); + } catch (InvalidFormatException $exception) { + // Possibly current language expect a dot after short name, but it's missing + if ($locale !== null && !mb_strlen($name) < 4 && !str_ends_with($name, '.')) { + try { + return self::from(CarbonImmutable::parseFromLocale($name.'.', $locale)->dayOfWeek); + } catch (InvalidFormatException) { + // Throw previous error + } + } + + throw $exception; + } + } + + public function next(?CarbonImmutable $now = null): CarbonImmutable + { + return $now?->modify($this->name) ?? new CarbonImmutable($this->name); + } + + public function locale(string $locale, ?CarbonImmutable $now = null): CarbonImmutable + { + return $this->next($now)->locale($locale); + } +} diff --git a/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/WrapperClock.php b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/WrapperClock.php new file mode 100644 index 000000000..7fb184fe5 --- /dev/null +++ b/data/web/inc/lib/vendor/nesbot/carbon/src/Carbon/WrapperClock.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DateTime; +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use Psr\Clock\ClockInterface as PsrClockInterface; +use RuntimeException; +use Symfony\Component\Clock\ClockInterface; + +final class WrapperClock implements ClockInterface +{ + public function __construct( + private PsrClockInterface|Factory|DateTimeInterface $currentClock, + ) { + } + + public function unwrap(): PsrClockInterface|Factory|DateTimeInterface + { + return $this->currentClock; + } + + public function getFactory(): Factory + { + if ($this->currentClock instanceof Factory) { + return $this->currentClock; + } + + if ($this->currentClock instanceof DateTime) { + $factory = new Factory(); + $factory->setTestNowAndTimezone($this->currentClock); + + return $factory; + } + + if ($this->currentClock instanceof DateTimeImmutable) { + $factory = new FactoryImmutable(); + $factory->setTestNowAndTimezone($this->currentClock); + + return $factory; + } + + $factory = new FactoryImmutable(); + $factory->setTestNowAndTimezone(fn () => $this->currentClock->now()); + + return $factory; + } + + private function nowRaw(): DateTimeInterface + { + if ($this->currentClock instanceof DateTimeInterface) { + return $this->currentClock; + } + + if ($this->currentClock instanceof Factory) { + return $this->currentClock->__call('now', []); + } + + return $this->currentClock->now(); + } + + public function now(): DateTimeImmutable + { + $now = $this->nowRaw(); + + return $now instanceof DateTimeImmutable + ? $now + : new CarbonImmutable($now); + } + + /** + * @template T of CarbonInterface + * + * @param class-string $class + * + * @return T + */ + public function nowAs(string $class, DateTimeZone|string|int|null $timezone = null): CarbonInterface + { + $now = $this->nowRaw(); + $date = $now instanceof $class ? $now : $class::instance($now); + + return $timezone === null ? $date : $date->setTimezone($timezone); + } + + public function nowAsCarbon(DateTimeZone|string|int|null $timezone = null): CarbonInterface + { + $now = $this->nowRaw(); + + return $now instanceof CarbonInterface + ? ($timezone === null ? $now : $now->setTimezone($timezone)) + : $this->dateAsCarbon($now, $timezone); + } + + private function dateAsCarbon(DateTimeInterface $date, DateTimeZone|string|int|null $timezone): CarbonInterface + { + return $date instanceof DateTimeImmutable + ? new CarbonImmutable($date, $timezone) + : new Carbon($date, $timezone); + } + + public function sleep(float|int $seconds): void + { + if ($seconds === 0 || $seconds === 0.0) { + return; + } + + if ($seconds < 0) { + throw new RuntimeException('Expected positive number of seconds, '.$seconds.' given'); + } + + if ($this->currentClock instanceof DateTimeInterface) { + $this->currentClock = $this->addSeconds($this->currentClock, $seconds); + + return; + } + + if ($this->currentClock instanceof ClockInterface) { + $this->currentClock->sleep($seconds); + + return; + } + + $this->currentClock = $this->addSeconds($this->currentClock->now(), $seconds); + } + + public function withTimeZone(DateTimeZone|string $timezone): static + { + if ($this->currentClock instanceof ClockInterface) { + return new self($this->currentClock->withTimeZone($timezone)); + } + + $now = $this->currentClock instanceof DateTimeInterface + ? $this->currentClock + : $this->currentClock->now(); + + if (!($now instanceof DateTimeImmutable)) { + $now = clone $now; + } + + if (\is_string($timezone)) { + $timezone = new DateTimeZone($timezone); + } + + return new self($now->setTimezone($timezone)); + } + + private function addSeconds(DateTimeInterface $date, float|int $seconds): DateTimeInterface + { + $secondsPerHour = CarbonInterface::SECONDS_PER_MINUTE * CarbonInterface::MINUTES_PER_HOUR; + $hours = number_format( + floor($seconds / $secondsPerHour), + thousands_separator: '', + ); + $microseconds = number_format( + ($seconds - $hours * $secondsPerHour) * CarbonInterface::MICROSECONDS_PER_SECOND, + thousands_separator: '', + ); + + if (!($date instanceof DateTimeImmutable)) { + $date = clone $date; + } + + if ($hours !== '0') { + $date = $date->modify("$hours hours"); + } + + if ($microseconds !== '0') { + $date = $date->modify("$microseconds microseconds"); + } + + return $date; + } +} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh b/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh deleted file mode 100755 index b4a5ba31c..000000000 --- a/data/web/inc/lib/vendor/paragonie/random_compat/build-phar.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) - -php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/composer.json b/data/web/inc/lib/vendor/paragonie/random_compat/composer.json deleted file mode 100644 index f2b9c4e51..000000000 --- a/data/web/inc/lib/vendor/paragonie/random_compat/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "paragonie/random_compat", - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "random", - "polyfill", - "pseudorandom" - ], - "license": "MIT", - "type": "library", - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "support": { - "issues": "https://github.com/paragonie/random_compat/issues", - "email": "info@paragonie.com", - "source": "https://github.com/paragonie/random_compat" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "vimeo/psalm": "^1", - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - } -} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey deleted file mode 100644 index eb50ebfcd..000000000 --- a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PUBLIC KEY----- -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm -pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p -+h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc ------END PUBLIC KEY----- diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc deleted file mode 100644 index 6a1d7f300..000000000 --- a/data/web/inc/lib/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN PGP SIGNATURE----- -Version: GnuPG v2.0.22 (MingW32) - -iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip -QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg -1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW -NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA -NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV -JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= -=B6+8 ------END PGP SIGNATURE----- diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php b/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php deleted file mode 100644 index c7731a56f..000000000 --- a/data/web/inc/lib/vendor/paragonie/random_compat/lib/random.php +++ /dev/null @@ -1,32 +0,0 @@ -buildFromDirectory(dirname(__DIR__).'/lib'); -rename( - dirname(__DIR__).'/lib/index.php', - dirname(__DIR__).'/lib/random.php' -); - -/** - * If we pass an (optional) path to a private key as a second argument, we will - * sign the Phar with OpenSSL. - * - * If you leave this out, it will produce an unsigned .phar! - */ -if ($argc > 1) { - if (!@is_readable($argv[1])) { - echo 'Could not read the private key file:', $argv[1], "\n"; - exit(255); - } - $pkeyFile = file_get_contents($argv[1]); - - $private = openssl_get_privatekey($pkeyFile); - if ($private !== false) { - $pkey = ''; - openssl_pkey_export($private, $pkey); - $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); - - /** - * Save the corresponding public key to the file - */ - if (!@is_readable($dist.'/random_compat.phar.pubkey')) { - $details = openssl_pkey_get_details($private); - file_put_contents( - $dist.'/random_compat.phar.pubkey', - $details['key'] - ); - } - } else { - echo 'An error occurred reading the private key from OpenSSL.', "\n"; - exit(255); - } -} diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php b/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php deleted file mode 100644 index d71d1b818..000000000 --- a/data/web/inc/lib/vendor/paragonie/random_compat/psalm-autoload.php +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml index 7931d00bc..2cce32306 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/.github/workflows/main.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [7.2, 7.3, 7.4, 8.0] + php: [7.3, 7.4, 8.0, 8.1] dependency-version: [prefer-lowest, prefer-stable] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v1 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v1 - name: Install dependencies run: composer update --no-progress --ignore-platform-reqs @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v1 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json index e9861a241..8251d7a95 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/composer.json @@ -46,7 +46,7 @@ "ext-mailparse": "*" }, "require-dev": { - "phpunit/phpunit": "^8.0", + "phpunit/phpunit": "^9.3", "php-coveralls/php-coveralls": "^2.2", "squizlabs/php_codesniffer": "^3.5" }, diff --git a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php index a7b3cb0d3..d2211b7c0 100644 --- a/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php +++ b/data/web/inc/lib/vendor/php-mime-mail-parser/php-mime-mail-parser/src/MimePart.php @@ -80,6 +80,7 @@ class MimePart implements \ArrayAccess /** * ArrayAccess */ + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if (is_null($offset)) { @@ -92,6 +93,7 @@ class MimePart implements \ArrayAccess /** * ArrayAccess */ + #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->part[$offset]); @@ -100,6 +102,7 @@ class MimePart implements \ArrayAccess /** * ArrayAccess */ + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->part[$offset]); @@ -108,6 +111,7 @@ class MimePart implements \ArrayAccess /** * ArrayAccess */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return isset($this->part[$offset]) ? $this->part[$offset] : null; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md index 81b089763..646649c9a 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/README.md @@ -1,3 +1,5 @@ +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://supportukrainenow.org/) + ![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png) # PHPMailer – A full-featured email creation and transfer class for PHP @@ -8,43 +10,45 @@ [![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer) [![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer) [![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer/badge)](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer) ## Features - Probably the world's most popular code for sending email from PHP! - Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more - Integrated SMTP support – send without a local mail server -- Send emails with multiple To, CC, BCC and Reply-to addresses +- Send emails with multiple To, CC, BCC, and Reply-to addresses - Multipart/alternative emails for mail clients that do not read HTML email - Add attachments, including inline - Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings -- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports +- Full UTF-8 support when using servers that support `SMTPUTF8`. +- Support for iCal events in multiparts and attachments +- SMTP authentication with `LOGIN`, `PLAIN`, `CRAM-MD5`, and `XOAUTH2` mechanisms over SMTPS and SMTP+STARTTLS transports - Validates email addresses automatically - Protects against header injection attacks - Error messages in over 50 languages! - DKIM and S/MIME signing support -- Compatible with PHP 5.5 and later, including PHP 8.1 +- Compatible with PHP 5.5 and later, including PHP 8.4 - Namespaced to prevent name clashes - Much more! ## Why you might need it -Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments. +Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as authentication, HTML messages, and attachments. Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe! The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost. *Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that -you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/) -, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail) etc. +you should look at before rolling your own. Try [Symfony Mailer](https://symfony.com/doc/current/mailer.html), [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc. ## License -This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. +This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. ## Installation & loading PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file: ```json -"phpmailer/phpmailer": "^6.5" +"phpmailer/phpmailer": "^6.12.0" ``` or run @@ -55,7 +59,8 @@ composer require phpmailer/phpmailer Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer. -If you want to use the Gmail XOAUTH2 authentication class, you will also need to add a dependency on the `league/oauth2-client` package in your `composer.json`. +If you want to use XOAUTH2 authentication, you will also need to add a dependency on the `league/oauth2-client` and appropriate service adapters package in your `composer.json`, or take a look at +by @decomplexity's [SendOauth2 wrapper](https://github.com/decomplexity/SendOauth2), especially if you're using Microsoft services. Alternatively, if you're not using Composer, you can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually: @@ -70,7 +75,7 @@ require 'path/to/PHPMailer/src/PHPMailer.php'; require 'path/to/PHPMailer/src/SMTP.php'; ``` -If you're not using the `SMTP` class explicitly (you're probably not), you don't need a `use` line for the SMTP class. Even if you're not using exceptions, you do still need to load the `Exception` class as it is used internally. +If you're not using the `SMTP` class explicitly (you're probably not), you don't need a `use` line for it. Even if you're not using exceptions, you do still need to load the `Exception` class as it is used internally. ## Legacy versions PHPMailer 5.2 (which is compatible with PHP 5.0 — 7.0) is no longer supported, even for security updates. You will find the latest version of 5.2 in the [5.2-stable branch](https://github.com/PHPMailer/PHPMailer/tree/5.2-stable). If you're using PHP 5.5 or later (which you should be), switch to the 6.x releases. @@ -91,7 +96,7 @@ use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use PHPMailer\PHPMailer\Exception; -//Load Composer's autoloader +//Load Composer's autoloader (created by composer, not included with PHPMailer) require 'vendor/autoload.php'; //Create an instance; passing `true` enables exceptions @@ -133,21 +138,21 @@ try { } ``` -You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through gmail, building contact forms, sending to mailing lists, and more. +You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more. If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance. That's it. You should now be ready to use PHPMailer! ## Localization -PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: +PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: ```php //To load the French version $mail->setLanguage('fr', '/optional/path/to/language/directory/'); ``` -We welcome corrections and new languages – if you're looking for corrections, run the [PHPMailerLangTest.php](https://github.com/PHPMailer/PHPMailer/tree/master/test/PHPMailerLangTest.php) script in the tests folder and it will show any missing translations. +We welcome corrections and new languages – if you're looking for corrections, run the [Language/TranslationCompletenessTest.php](https://github.com/PHPMailer/PHPMailer/blob/master/test/Language/TranslationCompletenessTest.php) script in the tests folder and it will show any missing translations. ## Documentation Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated. @@ -158,9 +163,9 @@ To reduce PHPMailer's deployed code footprint, examples are not included if you Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). -You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](https://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailer/PHPMailerTest.php) a good reference for how to do various operations such as encryption. -If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). +If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](https://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). ## Tests [PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions. @@ -175,9 +180,9 @@ Please disclose any vulnerabilities found responsibly – report security issues See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security). ## Contributing -Please submit bug reports, suggestions and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). +Please submit bug reports, suggestions, and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). -We're particularly interested in fixing edge-cases, expanding test coverage and updating translations. +We're particularly interested in fixing edge cases, expanding test coverage, and updating translations. If you found a mistake in the docs, or want to add something, go ahead and amend the wiki – anyone can edit it. @@ -201,7 +206,7 @@ Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard c Available as part of the Tidelift Subscription. The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial -support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and +support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) @@ -209,7 +214,7 @@ use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-php See [changelog](changelog.md). ## History -- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](https://sourceforge.net/projects/phpmailer/). - [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004. - Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. - Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008. @@ -219,9 +224,9 @@ See [changelog](changelog.md). ### What's changed since moving from SourceForge? - Official successor to the SourceForge and Google Code projects. - Test suite. -- Continuous integration with Github Actions. +- Continuous integration with GitHub Actions. - Composer support. - Public development. - Additional languages and language strings. - CRAM-MD5 authentication support. -- Preserves full repo history of authors, commits and branches from the original SourceForge project. +- Preserves full repo history of authors, commits, and branches from the original SourceForge project. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md index 035a87f7b..4f34026df 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/SECURITY.md @@ -13,13 +13,13 @@ PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr. -PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. +PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it is not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity. PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer). -PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](http://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). +PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/SMTPUTF8.md b/data/web/inc/lib/vendor/phpmailer/phpmailer/SMTPUTF8.md new file mode 100644 index 000000000..ca284ee21 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/SMTPUTF8.md @@ -0,0 +1,48 @@ +# A short history of UTF-8 in email + +## Background + +For most of its existence, SMTP has been a 7-bit channel, only supporting US-ASCII characters. This has been a problem for many languages, especially those that use non-Latin scripts, and has led to the development of various workarounds. + +The first major improvement, introduced in 1994 in [RFC 1652](https://www.rfc-editor.org/rfc/rfc1652) and extended in 2011 in [RFC 6152](https://www.rfc-editor.org/rfc/rfc6152), was the addition of the `8BITMIME` SMTP extension, which allowed raw 8-bit data to be included in message bodies sent over SMTP. +This allowed the message *contents* to contain 8-bit data, including things like UTF-8 text, even though the SMTP protocol itself was still firmly 7-bit. This worked by having the server switch to 8-bit after the headers, and then back to 7-bit after the completion of a `DATA` command. + +From 1996, messages could support [RFC 2047 encoding](https://www.rfc-editor.org/rfc/rfc2047), which permitted inserting characters from any character set into header *values* (but not names), but only by encoding them in somewhat unreadable ways to allow them to survive passage through a 7-bit channel. An example with a subject of "Schrödinger's cat" would be: + +``` +Subject: =?utf-8?Q=Schr=C3=B6dinger=92s_Cat?= +``` + +Here the accented `ö` is encoded as `=C3=B6`, which is the UTF-8 encoding of the 2-byte character, and the whole thing is wrapped in `=?utf-8?Q?` to indicate that it uses the UTF-8 charset and `quoted-printable` encoding. This is a bit of a hack, and not very human-friendly, but it works. + +Similarly, 8-bit message bodies could be encoded using the same `quoted-printable` and `base64` content transfer encoding (CTE) schemes, which preserved the 8-bit content while encoding it in a format that could survive transmission through a 7-bit channel. + +Domain names were originally also stuck in a 7-bit world, actually even more constrained to only a subset of the US-ASCII character set. But of course, many people want to have domains in their own language/script. Internationalized domain name (IDN) permitted this, using yet another complex encoding scheme called punycode, defined for domain names in 2003 in [RFC 3492](https://www.rfc-editor.org/rfc/rfc3492). This finally allowed the domain part (after the `@`) of email addresses to contain UTF-8, though it was actually an illusion preserved by email client applications. For example, an address of +`user@café.example.com` translates to +`user@xn--caf-dma.example.com` in punycode, rendering it mostly unreadable, but 7-bit friendly, and remaining compatible with email clients that don't know about IDN. + +The one remaining part of email that could not handle UTF-8 is the local part of email addresses (the part before the `@`). + +I've only mentioned UTF-8 here, but most of these approaches also allowed other character sets that were popular, such as [the ISO-8859 family](https://en.wikipedia.org/wiki/ISO/IEC_8859). However, UTF-8 solves so many problems that these other character sets are gradually falling out of favour, as UTF-8 can support all languages. + +This patchwork of overlapping approaches has served us well, but we have to admit that it's a mess. + +## SMTPUTF8 + +`SMTPUTF8` is another SMTP extension, defined in [RFC 6531](https://www.rfc-editor.org/rfc/rfc6531) in 2012. This essentially solves the whole problem, allowing the entire SMTP conversation — commands, headers, and message bodies — to be sent in raw, unencoded UTF-8. + +But there's a problem with this approach: adoption. If you send a UTF-8 message to a recipient whose mail server doesn't support this format, the sender has to somehow downgrade the message to make it survive a transition to 7-bit. This is a hard problem to solve, especially since there is no way to make a 7-bit system support UTF-8 in the local parts of addresses. This downgrade problem is what held up the adoption of `SMTPUTF8` in PHPMailer for many years, but in that time the *de facto* approach has become to simply fail in that situation, and tell the recipient it's time they upgraded their mail server 😅. + +The vast majority of large email providers (gmail, Yahoo, Microsoft, etc), mail servers (postfix, exim, IIS, etc), and mail clients (Apple Mail, Outlook, Thunderbird, etc) now all support SMTPUTF8, so the need for backward compatibility is no longer what it was. + +## SMTPUTF8 in PHPMailer + +Several other PHP email libraries have implemented a halfway solution to `SMTPUTF8`, adding only the ability to support UTF-8 in email addresses, not elsewhere in the protocol. I wanted PHPMailer to do it "the right way", and this has taken much longer. PHPMailer now supports UTF-8 everywhere, and does not need to use transfer or header encodings for UTF-8 text when connecting to an `SMTPUTF8`-capable mail server. + +This support is handled automatically: if you add an email address that requires UTF-8, PHPMailer will use UTF-8 for everything. If not, it will fall back to 7-bit and encode the message as necessary. + +The one place you will need to be careful is in the selection of the address validator. By default, PHPMailer uses PHP's built-in `filter_var` validator, which does not allow UTF-8 email addresses. When PHPMailer spots that you have submitted a UTF-8 address, but have not altered the default validator, it will automatically switch to using a UTF-8-compatible validator. As soon as you do this, any SMTP connection you make will *require* that the server you connect to supports `SMTPUTF8`. You can select this validator explicitly by setting `PHPMailer::$validator = 'eai'` (an acronym for Email Address Internationalization). + +### Postfix gotcha + +Postfix has supported `SMTPUTF8` for a long time, but it has a peculiarity that it does not always advertise that it does so. However, rather surprisingly, if you use UTF-8 in the conversation, it will work anyway. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION index cd802a1ec..d4e6cb429 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/VERSION @@ -1 +1 @@ -6.6.0 \ No newline at end of file +6.12.0 diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json b/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json index b13732bed..7b008b7c5 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/composer.json @@ -28,7 +28,8 @@ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true - } + }, + "lock": false }, "require": { "php": ">=5.5.0", @@ -37,21 +38,24 @@ "ext-hash": "*" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3.1", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication", "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" }, "autoload": { @@ -70,7 +74,7 @@ "test": "./vendor/bin/phpunit --no-coverage", "coverage": "./vendor/bin/phpunit", "lint": [ - "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php,phps --exclude vendor --exclude .git --exclude build" + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php,phps --exclude vendor --exclude .git --exclude build" ] } } diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/get_oauth_token.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/get_oauth_token.php index befdc34a5..0e54a00b6 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/get_oauth_token.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/get_oauth_token.php @@ -12,7 +12,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -36,7 +36,7 @@ namespace PHPMailer\PHPMailer; * Aliases for League Provider Classes * Make sure you have added these to your composer.json and run `composer install` * Plenty to choose from here: - * @see http://oauth2-client.thephpleague.com/providers/thirdparty/ + * @see https://oauth2-client.thephpleague.com/providers/thirdparty/ */ //@see https://github.com/thephpleague/oauth2-google use League\OAuth2\Client\Provider\Google; @@ -44,14 +44,31 @@ use League\OAuth2\Client\Provider\Google; use Hayageek\OAuth2\Client\Provider\Yahoo; //@see https://github.com/stevenmaguire/oauth2-microsoft use Stevenmaguire\OAuth2\Client\Provider\Microsoft; +//@see https://github.com/greew/oauth2-azure-provider +use Greew\OAuth2\Client\Provider\Azure; -if (!isset($_GET['code']) && !isset($_GET['provider'])) { +if (!isset($_GET['code']) && !isset($_POST['provider'])) { ?> -Select Provider:
-Google
-Yahoo
-Microsoft/Outlook/Hotmail/Live/Office365
+ +
+

Select Provider

+ +
+ +
+ +
+ +
+

Enter id and secret

+

These details are obtained by setting up an app in your provider's developer console. +

+

ClientId:

+

ClientSecret:

+

TenantID (only relevant for Azure):

+ +
[ + 'https://outlook.office.com/SMTP.Send', + 'offline_access' + ] + ]; + break; } if (null === $provider) { @@ -142,5 +178,5 @@ if (!isset($_GET['code'])) { ); //Use this to interact with an API on the users behalf //Use this to get a new access token if the old one expires - echo 'Refresh Token: ', $token->getRefreshToken(); + echo 'Refresh Token: ', htmlspecialchars($token->getRefreshToken()); } diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php new file mode 100644 index 000000000..327dfbafa --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্ৰুটি: প্ৰমাণীকৰণ কৰিব নোৱাৰি'; +$PHPMAILER_LANG['buggy_php'] = 'আপোনাৰ PHP সংস্কৰণ এটা বাগৰ দ্বাৰা প্ৰভাৱিত হয় যাৰ ফলত নষ্ট বাৰ্তা হব পাৰে । ইয়াক সমাধান কৰিবলে, প্ৰেৰণ কৰিবলে SMTP ব্যৱহাৰ কৰক, আপোনাৰ php.ini ত mail.add_x_header বিকল্প নিষ্ক্ৰিয় কৰক, MacOS বা Linux লৈ সলনি কৰক, বা আপোনাৰ PHP সংস্কৰণ 7.0.17+ বা 7.1.3+ লৈ সলনি কৰক ।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্ৰুটি: SMTP চাৰ্ভাৰৰ সৈতে সংযোগ কৰিবলে অক্ষম'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্ৰুটি: তথ্য গ্ৰহণ কৰা হোৱা নাই'; +$PHPMAILER_LANG['empty_message'] = 'বাৰ্তাৰ মূখ্য অংশ খালী।'; +$PHPMAILER_LANG['encoding'] = 'অজ্ঞাত এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'এক্সিকিউট কৰিব নোৱাৰি: '; +$PHPMAILER_LANG['extension_missing'] = 'সম্প্ৰসাৰণ নোহোৱা হৈছে: '; +$PHPMAILER_LANG['file_access'] = 'ফাইল অভিগম কৰিবলে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্ৰুটি: ফাইল খোলিবলৈ অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্ৰেৰকৰ ঠিকনা(সমূহ) ব্যৰ্থ: '; +$PHPMAILER_LANG['instantiate'] = 'মেইল ফাংচনৰ এটা উদাহৰণ সৃষ্টি কৰিবলে অক্ষম'; +$PHPMAILER_LANG['invalid_address'] = 'প্ৰেৰণ কৰিব নোৱাৰি: অবৈধ ইমেইল ঠিকনা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডাৰৰ নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোষ্টেন্ট্ৰি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলাৰ সমৰ্থিত নহয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপুনি অন্ততঃ এটা গন্তব্য ইমেইল ঠিকনা দিব লাগিব'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্ৰুটি: নিম্নলিখিত গন্তব্যস্থানসমূহ ব্যৰ্থ: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষৰ কৰাত ব্যৰ্থ: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিৰিক্ত SMTP তথ্য: '; +$PHPMAILER_LANG['smtp_detail'] = 'বিৱৰণ:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যৰ্থ'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP চাৰ্ভাৰৰ ত্ৰুটি: '; +$PHPMAILER_LANG['variable_set'] = 'চলক নিৰ্ধাৰণ কৰিব পৰা নগল: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত সম্প্ৰসাৰণ: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php new file mode 100644 index 000000000..473651080 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্রুটি: প্রমাণীকরণ করতে অক্ষম৷'; +$PHPMAILER_LANG['buggy_php'] = 'আপনার PHP সংস্করণ একটি বাগ দ্বারা প্রভাবিত হয় যার ফলে দূষিত বার্তা হতে পারে। এটি ঠিক করতে, পাঠাতে SMTP ব্যবহার করুন, আপনার php.ini এ mail.add_x_header বিকল্পটি নিষ্ক্রিয় করুন, MacOS বা Linux-এ স্যুইচ করুন, অথবা আপনার PHP সংস্করণকে 7.0.17+ বা 7.1.3+ এ পরিবর্তন করুন।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্রুটি: SMTP সার্ভারের সাথে সংযোগ করতে অক্ষম৷'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্রুটি: ডেটা গ্রহণ করা হয়নি৷'; +$PHPMAILER_LANG['empty_message'] = 'বার্তার অংশটি খালি।'; +$PHPMAILER_LANG['encoding'] = 'অজানা এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'নির্বাহ করতে অক্ষম: '; +$PHPMAILER_LANG['extension_missing'] = 'এক্সটেনশন অনুপস্থিত:'; +$PHPMAILER_LANG['file_access'] = 'ফাইল অ্যাক্সেস করতে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্রুটি: ফাইল খুলতে অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্রেরকের ঠিকানা(গুলি) ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['instantiate'] = 'মেল ফাংশনের একটি উদাহরণ তৈরি করতে অক্ষম৷'; +$PHPMAILER_LANG['invalid_address'] = 'পাঠাতে অক্ষম: অবৈধ ইমেল ঠিকানা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডার নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোস্টেন্ট্রি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হোস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলার সমর্থিত নয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপনাকে অবশ্যই অন্তত একটি গন্তব্য ইমেল ঠিকানা প্রদান করতে হবে৷'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্রুটি: নিম্নলিখিত গন্তব্যগুলি ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষর করতে ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কোড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিরিক্ত SMTP তথ্য:'; +$PHPMAILER_LANG['smtp_detail'] = 'বর্ণনা: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যর্থ হয়েছে৷'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP সার্ভার ত্রুটি: '; +$PHPMAILER_LANG['variable_set'] = 'পরিবর্তনশীল সেট করা যায়নি: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত এক্সটেনশন: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php deleted file mode 100644 index 500c95267..000000000 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ - -$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:身份验证失败。'; -$PHPMAILER_LANG['connect_host'] = 'SMTP 错误: 不能连接SMTP主机。'; -$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误: 数据不可接受。'; -//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; -$PHPMAILER_LANG['encoding'] = '未知编码:'; -$PHPMAILER_LANG['execute'] = '不能执行: '; -$PHPMAILER_LANG['file_access'] = '不能访问文件:'; -$PHPMAILER_LANG['file_open'] = '文件错误:不能打开文件:'; -$PHPMAILER_LANG['from_failed'] = '下面的发送地址邮件发送失败了: '; -$PHPMAILER_LANG['instantiate'] = '不能实现mail方法。'; -//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; -$PHPMAILER_LANG['mailer_not_supported'] = ' 您所选择的发送邮件的方法并不支持。'; -$PHPMAILER_LANG['provide_address'] = '您必须提供至少一个 收信人的email地址。'; -$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误: 下面的 收件人失败了: '; -//$PHPMAILER_LANG['signing'] = 'Signing Error: '; -//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; -//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; -//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; -//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php index 1edba1d7a..db9a1ef5b 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php @@ -9,21 +9,28 @@ */ $PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Login mislykkedes.'; +$PHPMAILER_LANG['buggy_php'] = 'Din version af PHP er berørt af en fejl, som gør at dine beskeder muligvis vises forkert. For at rette dette kan du skifte til SMTP, slå mail.add_x_header headeren i din php.ini fil fra, skifte til MacOS eller Linux eller opgradere din version af PHP til 7.0.17+ eller 7.1.3+.'; $PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data blev ikke accepteret.'; $PHPMAILER_LANG['empty_message'] = 'Meddelelsen er uden indhold'; $PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: '; $PHPMAILER_LANG['execute'] = 'Kunne ikke afvikle: '; +$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: '; $PHPMAILER_LANG['file_access'] = 'Kunne ikke tilgå filen: '; $PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: '; $PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: '; $PHPMAILER_LANG['instantiate'] = 'Email funktionen kunne ikke initialiseres.'; $PHPMAILER_LANG['invalid_address'] = 'Udgyldig adresse: '; +$PHPMAILER_LANG['invalid_header'] = 'Ugyldig header navn eller værdi'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vært: '; $PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.'; $PHPMAILER_LANG['provide_address'] = 'Indtast mindst en modtagers email adresse.'; -$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere er forkerte: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere fejlede: '; $PHPMAILER_LANG['signing'] = 'Signeringsfejl: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP kode: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Yderligere SMTP info: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalje: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: '; $PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: '; -$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php index b3d5ca94b..339ee5753 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php @@ -5,22 +5,29 @@ * @package PHPMailer */ -$PHPMAILER_LANG['authenticate'] = 'SMTP Σφάλμα: Αδυναμία πιστοποίησης (authentication).'; -$PHPMAILER_LANG['connect_host'] = 'SMTP Σφάλμα: Αδυναμία σύνδεσης στον SMTP-Host.'; -$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Σφάλμα: Τα δεδομένα δεν έγιναν αποδεκτά.'; -$PHPMAILER_LANG['empty_message'] = 'Το E-Mail δεν έχει περιεχόμενο .'; -$PHPMAILER_LANG['encoding'] = 'Αγνωστο Encoding-Format: '; -$PHPMAILER_LANG['execute'] = 'Αδυναμία εκτέλεσης ακόλουθης εντολής: '; -$PHPMAILER_LANG['file_access'] = 'Αδυναμία προσπέλασης του αρχείου: '; -$PHPMAILER_LANG['file_open'] = 'Σφάλμα Αρχείου: Δεν είναι δυνατό το άνοιγμα του ακόλουθου αρχείου: '; -$PHPMAILER_LANG['from_failed'] = 'Η παρακάτω διεύθυνση αποστολέα δεν είναι σωστή: '; -$PHPMAILER_LANG['instantiate'] = 'Αδυναμία εκκίνησης Mail function.'; -$PHPMAILER_LANG['invalid_address'] = 'Το μήνυμα δεν εστάλη, η διεύθυνση δεν είναι έγκυρη: '; +$PHPMAILER_LANG['authenticate'] = 'Σφάλμα SMTP: Αδυναμία πιστοποίησης.'; +$PHPMAILER_LANG['buggy_php'] = 'Η έκδοση PHP που χρησιμοποιείτε παρουσιάζει σφάλμα που μπορεί να έχει ως αποτέλεσμα κατεστραμένα μηνύματα. Για να το διορθώσετε, αλλάξτε τον τρόπο αποστολής σε SMTP, απενεργοποιήστε την επιλογή mail.add_x_header στο αρχείο php.ini, αλλάξτε λειτουργικό σε MacOS ή Linux ή αναβαθμίστε την PHP σε έκδοση 7.0.17+ ή 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'Σφάλμα SMTP: Αδυναμία σύνδεσης με τον φιλοξενητή SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Σφάλμα SMTP: Μη αποδεκτά δεδομένα.'; +$PHPMAILER_LANG['empty_message'] = 'Η ηλεκτρονική επιστολή δεν έχει περιεχόμενο.'; +$PHPMAILER_LANG['encoding'] = 'Άγνωστη μορφή κωδικοποίησης: '; +$PHPMAILER_LANG['execute'] = 'Αδυναμία εκτέλεσης: '; +$PHPMAILER_LANG['extension_missing'] = 'Απουσία επέκτασης: '; +$PHPMAILER_LANG['file_access'] = 'Αδυναμία πρόσβασης στο αρχείο: '; +$PHPMAILER_LANG['file_open'] = 'Σφάλμα Αρχείου: Αδυναμία ανοίγματος αρχείου: '; +$PHPMAILER_LANG['from_failed'] = 'Η ακόλουθη διεύθυνση αποστολέα δεν είναι σωστή: '; +$PHPMAILER_LANG['instantiate'] = 'Αδυναμία εκκίνησης συνάρτησης Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Μη έγκυρη διεύθυνση: '; +$PHPMAILER_LANG['invalid_header'] = 'Μη έγκυρο όνομα κεφαλίδας ή τιμή'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Μη έγκυρη εισαγωγή φιλοξενητή: '; +$PHPMAILER_LANG['invalid_host'] = 'Μη έγκυρος φιλοξενητής: '; $PHPMAILER_LANG['mailer_not_supported'] = ' mailer δεν υποστηρίζεται.'; -$PHPMAILER_LANG['provide_address'] = 'Παρακαλούμε δώστε τουλάχιστον μια e-mail διεύθυνση παραλήπτη.'; -$PHPMAILER_LANG['recipients_failed'] = 'SMTP Σφάλμα: Οι παρακάτω διευθύνσεις παραλήπτη δεν είναι έγκυρες: '; +$PHPMAILER_LANG['provide_address'] = 'Δώστε τουλάχιστον μια ηλεκτρονική διεύθυνση παραλήπτη.'; +$PHPMAILER_LANG['recipients_failed'] = 'Σφάλμα SMTP: Οι παρακάτω διευθύνσεις παραλήπτη δεν είναι έγκυρες: '; $PHPMAILER_LANG['signing'] = 'Σφάλμα υπογραφής: '; -$PHPMAILER_LANG['smtp_connect_failed'] = 'Αποτυχία σύνδεσης στον SMTP Server.'; -$PHPMAILER_LANG['smtp_error'] = 'Σφάλμα από τον SMTP Server: '; -$PHPMAILER_LANG['variable_set'] = 'Αδυναμία ορισμού ή αρχικοποίησης μεταβλητής: '; -//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; +$PHPMAILER_LANG['smtp_code'] = 'Κώδικάς SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Πρόσθετες πληροφορίες SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Αποτυχία σύνδεσης SMTP.'; +$PHPMAILER_LANG['smtp_detail'] = 'Λεπτομέρεια: '; +$PHPMAILER_LANG['smtp_error'] = 'Σφάλμα με τον διακομιστή SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Αδυναμία ορισμού ή επαναφοράς μεταβλητής: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php index 6ba746273..4e74bfb7b 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php @@ -4,24 +4,33 @@ * Spanish PHPMailer language file: refer to English translation for definitive list * @package PHPMailer * @author Matt Sturdy + * @author Crystopher Glodzienski Cardoso + * @author Daniel Cruz */ $PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.'; +$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP está afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.'; $PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.'; $PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.'; $PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.'; $PHPMAILER_LANG['encoding'] = 'Codificación desconocida: '; $PHPMAILER_LANG['execute'] = 'Imposible ejecutar: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; $PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: '; $PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: '; $PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: '; $PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.'; $PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: '; +$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Hostentry inválido: '; +$PHPMAILER_LANG['invalid_host'] = 'Host inválido: '; $PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.'; $PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.'; $PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: '; $PHPMAILER_LANG['signing'] = 'Error al firmar: '; +$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalle: '; $PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; $PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; -$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php index 243c05489..6d1e63739 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php @@ -20,7 +20,6 @@ $PHPMAILER_LANG['instantiate'] = 'mail-funktion luonti epäonnistui.'; $PHPMAILER_LANG['mailer_not_supported'] = 'postivälitintyyppiä ei tueta.'; $PHPMAILER_LANG['provide_address'] = 'Aseta vähintään yksi vastaanottajan sähköpostiosoite.'; $PHPMAILER_LANG['recipients_failed'] = 'SMTP-virhe: seuraava vastaanottaja osoite on virheellinen.'; -$PHPMAILER_LANG['encoding'] = 'Tuntematon koodaustyyppi: '; //$PHPMAILER_LANG['signing'] = 'Signing Error: '; //$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; //$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php index 38a7a8e7e..a6d582d83 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php @@ -6,7 +6,6 @@ * Some French punctuation requires a thin non-breaking space (U+202F) character before it, * for example before a colon or exclamation mark. * There is one of these characters between these quotes: " " - * @see http://unicode.org/udhr/n/notes_fra.html */ $PHPMAILER_LANG['authenticate'] = 'Erreur SMTP : échec de l’authentification.'; @@ -31,8 +30,7 @@ $PHPMAILER_LANG['recipients_failed'] = 'Erreur SMTP : les destinataires s $PHPMAILER_LANG['signing'] = 'Erreur de signature : '; $PHPMAILER_LANG['smtp_code'] = 'Code SMTP : '; $PHPMAILER_LANG['smtp_code_ex'] = 'Informations supplémentaires SMTP : '; -$PHPMAILER_LANG['smtp_connect_failed'] = 'La fonction SMTP connect() a échouée.'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'La fonction SMTP connect() a échoué.'; $PHPMAILER_LANG['smtp_detail'] = 'Détails : '; $PHPMAILER_LANG['smtp_error'] = 'Erreur du serveur SMTP : '; $PHPMAILER_LANG['variable_set'] = 'Impossible d’initialiser ou de réinitialiser une variable : '; -$PHPMAILER_LANG['extension_missing'] = 'Extension manquante : '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php index d973a3596..d2856e057 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php @@ -4,24 +4,32 @@ * Hindi PHPMailer language file: refer to English translation for definitive list * @package PHPMailer * @author Yash Karanke + * Rewrite and extension of the work by Jayanti Suthar */ $PHPMAILER_LANG['authenticate'] = 'SMTP त्रुटि: प्रामाणिकता की जांच नहीं हो सका। '; +$PHPMAILER_LANG['buggy_php'] = 'PHP का आपका संस्करण एक बग से प्रभावित है जिसके परिणामस्वरूप संदेश दूषित हो सकते हैं. इसे ठीक करने हेतु, भेजने के लिए SMTP का उपयोग करे, अपने php.ini में mail.add_x_header विकल्प को अक्षम करें, MacOS या Linux पर जाए, या अपने PHP संस्करण को 7.0.17+ या 7.1.3+ बदले.'; $PHPMAILER_LANG['connect_host'] = 'SMTP त्रुटि: SMTP सर्वर से कनेक्ट नहीं हो सका। '; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP त्रुटि: डेटा स्वीकार नहीं किया जाता है। '; $PHPMAILER_LANG['empty_message'] = 'संदेश खाली है। '; $PHPMAILER_LANG['encoding'] = 'अज्ञात एन्कोडिंग प्रकार। '; $PHPMAILER_LANG['execute'] = 'आदेश को निष्पादित करने में विफल। '; +$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: '; $PHPMAILER_LANG['file_access'] = 'फ़ाइल उपलब्ध नहीं है। '; $PHPMAILER_LANG['file_open'] = 'फ़ाइल त्रुटि: फाइल को खोला नहीं जा सका। '; $PHPMAILER_LANG['from_failed'] = 'प्रेषक का पता गलत है। '; $PHPMAILER_LANG['instantiate'] = 'मेल फ़ंक्शन कॉल नहीं कर सकता है।'; $PHPMAILER_LANG['invalid_address'] = 'पता गलत है। '; +$PHPMAILER_LANG['invalid_header'] = 'अमान्य हेडर नाम या मान'; +$PHPMAILER_LANG['invalid_hostentry'] = 'अमान्य hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'अमान्य होस्ट: '; $PHPMAILER_LANG['mailer_not_supported'] = 'मेल सर्वर के साथ काम नहीं करता है। '; $PHPMAILER_LANG['provide_address'] = 'आपको कम से कम एक प्राप्तकर्ता का ई-मेल पता प्रदान करना होगा।'; $PHPMAILER_LANG['recipients_failed'] = 'SMTP त्रुटि: निम्न प्राप्तकर्ताओं को पते भेजने में विफल। '; -$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि:। '; +$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP कोड: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'अतिरिक्त SMTP जानकारी: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP का connect () फ़ंक्शन विफल हुआ। '; +$PHPMAILER_LANG['smtp_detail'] = 'विवरण: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP सर्वर त्रुटि। '; $PHPMAILER_LANG['variable_set'] = 'चर को बना या संशोधित नहीं किया जा सकता। '; -$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php index c76f5264c..d01869cec 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php @@ -3,27 +3,35 @@ /** * Japanese PHPMailer language file: refer to English translation for definitive list * @package PHPMailer - * @author Mitsuhiro Yoshida + * @author Mitsuhiro Yoshida * @author Yoshi Sakai * @author Arisophy + * @author ARAKI Musashi */ $PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。'; +$PHPMAILER_LANG['buggy_php'] = 'ご利用のバージョンのPHPには不具合があり、メッセージが破損するおそれがあります。問題の解決は以下のいずれかを行ってください。SMTPでの送信に切り替える。php.iniのmail.add_x_headerをoffにする。MacOSまたはLinuxに切り替える。PHPバージョン7.0.17以降または7.1.3以降にアップグレードする。'; $PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。'; $PHPMAILER_LANG['empty_message'] = 'メール本文が空です。'; $PHPMAILER_LANG['encoding'] = '不明なエンコーディング: '; $PHPMAILER_LANG['execute'] = '実行できませんでした: '; +$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; $PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: '; $PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: '; $PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: '; $PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。'; $PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: '; -$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; +$PHPMAILER_LANG['invalid_header'] = '不正なヘッダー名またはその内容'; +$PHPMAILER_LANG['invalid_hostentry'] = '不正なホストエントリー: '; +$PHPMAILER_LANG['invalid_host'] = '不正なホスト: '; $PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。'; +$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; $PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: '; $PHPMAILER_LANG['signing'] = '署名エラー: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTPコード: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'SMTP追加情報: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。'; +$PHPMAILER_LANG['smtp_detail'] = '詳細: '; $PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: '; $PHPMAILER_LANG['variable_set'] = '変数が存在しません: '; -$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php new file mode 100644 index 000000000..cf3bda69f --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'هەڵەی SMTP : نەتوانرا کۆدەکە پشتڕاست بکرێتەوە '; +$PHPMAILER_LANG['connect_host'] = 'هەڵەی SMTP: نەتوانرا پەیوەندی بە سێرڤەرەوە بکات SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'هەڵەی SMTP: ئەو زانیاریانە قبوڵ نەکرا.'; +$PHPMAILER_LANG['empty_message'] = 'پەیامەکە بەتاڵە'; +$PHPMAILER_LANG['encoding'] = 'کۆدکردنی نەزانراو : '; +$PHPMAILER_LANG['execute'] = 'ناتوانرێت جێبەجێ بکرێت: '; +$PHPMAILER_LANG['file_access'] = 'ناتوانرێت دەستت بگات بە فایلەکە: '; +$PHPMAILER_LANG['file_open'] = 'هەڵەی پەڕگە(فایل): ناتوانرێت بکرێتەوە: '; +$PHPMAILER_LANG['from_failed'] = 'هەڵە لە ئاستی ناونیشانی نێرەر: '; +$PHPMAILER_LANG['instantiate'] = 'ناتوانرێت خزمەتگوزاری پۆستە پێشکەش بکرێت.'; +$PHPMAILER_LANG['invalid_address'] = 'نەتوانرا بنێردرێت ، چونکە ناونیشانی ئیمەیڵەکە نادروستە: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' مەیلەر پشتگیری ناکات'; +$PHPMAILER_LANG['provide_address'] = 'دەبێت ناونیشانی ئیمەیڵی لانیکەم یەک وەرگر دابین بکرێت.'; +$PHPMAILER_LANG['recipients_failed'] = ' هەڵەی SMTP: ئەم هەڵانەی خوارەوەشکستی هێنا لە ناردن بۆ هەردووکیان: '; +$PHPMAILER_LANG['signing'] = 'هەڵەی واژۆ: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect()پەیوەندی شکستی هێنا .'; +$PHPMAILER_LANG['smtp_error'] = 'هەڵەی ئاستی سێرڤەری SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'ناتوانرێت بیگۆڕیت یان دوبارە بینێریتەوە: '; +$PHPMAILER_LANG['extension_missing'] = 'درێژکراوە نەماوە: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php new file mode 100644 index 000000000..04d262c72 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php @@ -0,0 +1,27 @@ + + * @author João Vieira */ -$PHPMAILER_LANG['authenticate'] = 'Erro do SMTP: Não foi possível realizar a autenticação.'; -$PHPMAILER_LANG['connect_host'] = 'Erro do SMTP: Não foi possível realizar ligação com o servidor SMTP.'; -$PHPMAILER_LANG['data_not_accepted'] = 'Erro do SMTP: Os dados foram rejeitados.'; -$PHPMAILER_LANG['empty_message'] = 'A mensagem no e-mail está vazia.'; +$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Falha na autenticação.'; +$PHPMAILER_LANG['buggy_php'] = 'A sua versão do PHP tem um bug que pode causar mensagens corrompidas. Para resolver, utilize o envio por SMTP, desative a opção mail.add_x_header no ficheiro php.ini, mude para MacOS ou Linux, ou atualize o PHP para a versão 7.0.17+ ou 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Não foi possível ligar ao servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Dados não aceites.'; +$PHPMAILER_LANG['empty_message'] = 'A mensagem de e-mail está vazia.'; $PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; $PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; -$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder o ficheiro: '; -$PHPMAILER_LANG['file_open'] = 'Abertura do ficheiro: Não foi possível abrir o ficheiro: '; -$PHPMAILER_LANG['from_failed'] = 'Ocorreram falhas nos endereços dos seguintes remententes: '; -$PHPMAILER_LANG['instantiate'] = 'Não foi possível iniciar uma instância da função mail.'; -$PHPMAILER_LANG['invalid_address'] = 'Não foi enviado nenhum e-mail para o endereço de e-mail inválido: '; -$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; -$PHPMAILER_LANG['provide_address'] = 'Tem de fornecer pelo menos um endereço como destinatário do e-mail.'; -$PHPMAILER_LANG['recipients_failed'] = 'Erro do SMTP: O endereço do seguinte destinatário falhou: '; -$PHPMAILER_LANG['signing'] = 'Erro ao assinar: '; -$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; -$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; -$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; $PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder ao ficheiro: '; +$PHPMAILER_LANG['file_open'] = 'Erro ao abrir o ficheiro: '; +$PHPMAILER_LANG['from_failed'] = 'O envio falhou para o seguinte endereço do remetente: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: '; +$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor do cabeçalho inválido.'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Entrada de host inválida: '; +$PHPMAILER_LANG['invalid_host'] = 'Host inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'O cliente de e-mail não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Deve fornecer pelo menos um endereço de destinatário.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Falha no envio para os seguintes destinatários: '; +$PHPMAILER_LANG['signing'] = 'Erro ao assinar: '; +$PHPMAILER_LANG['smtp_code'] = 'Código SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Falha na função SMTP connect().'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalhes: '; +$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php index 8c8c5e817..8013f37c4 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php @@ -5,24 +5,32 @@ * @package PHPMailer * @author Alexey Chumakov * @author Foster Snowhill + * @author ProjectSoft */ -$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: не удалось пройти аутентификацию.'; +$PHPMAILER_LANG['buggy_php'] = 'В вашей версии PHP есть ошибка, которая может привести к повреждению сообщений. Чтобы исправить, переключитесь на отправку по SMTP, отключите опцию mail.add_x_header в ваш php.ini, переключитесь на MacOS или Linux или обновите PHP до версии 7.0.17+ или 7.1.3+.'; $PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.'; $PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; $PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: '; $PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; $PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; $PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: '; $PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; $PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().'; -$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.'; -$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; -$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: '; -$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; $PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: '; +$PHPMAILER_LANG['invalid_header'] = 'Неверное имя или значение заголовка'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Неверная запись хоста: '; +$PHPMAILER_LANG['invalid_host'] = 'Неверный хост: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['provide_address'] = 'Вы должны указать хотя бы один адрес электронной почты получателя.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: Ошибка следующих получателей: '; $PHPMAILER_LANG['signing'] = 'Ошибка подписи: '; -$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером'; +$PHPMAILER_LANG['smtp_code'] = 'Код SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Дополнительная информация SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером.'; +$PHPMAILER_LANG['smtp_detail'] = 'Детали: '; $PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: '; $PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: '; -$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php new file mode 100644 index 000000000..dce502aa0 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php @@ -0,0 +1,34 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP දෝෂය: සත්‍යාපනය අසාර්ථක විය.'; +$PHPMAILER_LANG['buggy_php'] = 'ඔබගේ PHP version එකෙහි පවතින දෝෂයක් නිසා email පණිවිඩ දෝෂ සහගත වීමේ හැකියාවක් ඇත. මෙය විසදීම සදහා SMTP භාවිතා කිරීම, mail.add_x_header INI setting එක අක්‍රීය කිරීම, MacOS හෝ Linux වලට මාරු වීම, හෝ ඔබගේ PHP version එක 7.0.17+ හෝ 7.1.3+ වලට අලුත් කිරීම කරගන්න.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP දෝෂය: සම්බන්ධ වීමට නොහැකි විය.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP දෝෂය: දත්ත පිළිගනු නොලැබේ.'; +$PHPMAILER_LANG['empty_message'] = 'පණිවිඩ අන්තර්ගතය හිස්'; +$PHPMAILER_LANG['encoding'] = 'නොදන්නා කේතනය: '; +$PHPMAILER_LANG['execute'] = 'ක්‍රියාත්මක කළ නොහැකි විය: '; +$PHPMAILER_LANG['extension_missing'] = 'Extension එක නොමැත: '; +$PHPMAILER_LANG['file_access'] = 'File එකට ප්‍රවේශ විය නොහැකි විය: '; +$PHPMAILER_LANG['file_open'] = 'File දෝෂය: File එක විවෘත කළ නොහැක: '; +$PHPMAILER_LANG['from_failed'] = 'පහත From ලිපිනයන් අසාර්ථක විය: '; +$PHPMAILER_LANG['instantiate'] = 'mail function එක ක්‍රියාත්මක කළ නොහැක.'; +$PHPMAILER_LANG['invalid_address'] = 'වලංගු නොවන ලිපිනය: '; +$PHPMAILER_LANG['invalid_header'] = 'වලංගු නොවන header නාමයක් හෝ අගයක්'; +$PHPMAILER_LANG['invalid_hostentry'] = 'වලංගු නොවන hostentry එකක්: '; +$PHPMAILER_LANG['invalid_host'] = 'වලංගු නොවන host එකක්: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer සහාය නොදක්වයි.'; +$PHPMAILER_LANG['provide_address'] = 'ඔබ අවම වශයෙන් එක් ලබන්නෙකුගේ ඊමේල් ලිපිනයක් සැපයිය යුතුය.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP දෝෂය: පහත ලබන්නන් අසමත් විය: '; +$PHPMAILER_LANG['signing'] = 'Sign කිරීමේ දෝෂය: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP කේතය: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'අමතර SMTP තොරතුරු: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP සම්බන්ධය අසාර්ථක විය.'; +$PHPMAILER_LANG['smtp_detail'] = 'තොරතුරු: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP දෝෂය: '; +$PHPMAILER_LANG['variable_set'] = 'Variable එක සැකසීමට හෝ නැවත සැකසීමට නොහැක: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php index f938f8020..3c45bc1c3 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php @@ -11,21 +11,28 @@ */ $PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.'; +$PHPMAILER_LANG['buggy_php'] = 'PHP sürümünüz iletilerin bozulmasına neden olabilecek bir hatadan etkileniyor. Bunu düzeltmek için, SMTP kullanarak göndermeye geçin, mail.add_x_header seçeneğini devre dışı bırakın php.ini dosyanızdaki mail.add_x_header seçeneğini devre dışı bırakın, MacOS veya Linux geçin veya PHP sürümünü 7.0.17+ veya 7.1.3+ sürümüne yükseltin,'; $PHPMAILER_LANG['connect_host'] = 'SMTP Hatası: SMTP sunucusuna bağlanılamadı.'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP Hatası: Veri kabul edilmedi.'; $PHPMAILER_LANG['empty_message'] = 'Mesajın içeriği boş'; $PHPMAILER_LANG['encoding'] = 'Bilinmeyen karakter kodlama: '; $PHPMAILER_LANG['execute'] = 'Çalıştırılamadı: '; +$PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: '; $PHPMAILER_LANG['file_access'] = 'Dosyaya erişilemedi: '; $PHPMAILER_LANG['file_open'] = 'Dosya Hatası: Dosya açılamadı: '; $PHPMAILER_LANG['from_failed'] = 'Belirtilen adreslere gönderme başarısız: '; $PHPMAILER_LANG['instantiate'] = 'Örnek e-posta fonksiyonu oluşturulamadı.'; $PHPMAILER_LANG['invalid_address'] = 'Geçersiz e-posta adresi: '; +$PHPMAILER_LANG['invalid_header'] = 'Geçersiz başlık adı veya değeri: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Geçersiz ana bilgisayar girişi: '; +$PHPMAILER_LANG['invalid_host'] = 'Geçersiz ana bilgisayar: '; $PHPMAILER_LANG['mailer_not_supported'] = ' e-posta kütüphanesi desteklenmiyor.'; $PHPMAILER_LANG['provide_address'] = 'En az bir alıcı e-posta adresi belirtmelisiniz.'; $PHPMAILER_LANG['recipients_failed'] = 'SMTP Hatası: Belirtilen alıcılara ulaşılamadı: '; $PHPMAILER_LANG['signing'] = 'İmzalama hatası: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP kodu: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'ek SMTP bilgileri: '; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() fonksiyonu başarısız.'; +$PHPMAILER_LANG['smtp_detail'] = 'SMTP SMTP Detayı: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: '; $PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: '; -$PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: '; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php new file mode 100644 index 000000000..0b9de0f12 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php @@ -0,0 +1,30 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP خرابی: تصدیق کرنے سے قاصر۔'; +$PHPMAILER_LANG['connect_host'] = 'SMTP خرابی: سرور سے منسلک ہونے سے قاصر۔'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP خرابی: ڈیٹا قبول نہیں کیا گیا۔'; +$PHPMAILER_LANG['empty_message'] = 'پیغام کی باڈی خالی ہے۔'; +$PHPMAILER_LANG['encoding'] = 'نامعلوم انکوڈنگ: '; +$PHPMAILER_LANG['execute'] = 'عمل کرنے کے قابل نہیں '; +$PHPMAILER_LANG['file_access'] = 'فائل تک رسائی سے قاصر:'; +$PHPMAILER_LANG['file_open'] = 'فائل کی خرابی: فائل کو کھولنے سے قاصر:'; +$PHPMAILER_LANG['from_failed'] = 'درج ذیل بھیجنے والے کا پتہ ناکام ہو گیا:'; +$PHPMAILER_LANG['instantiate'] = 'میل فنکشن کی مثال بنانے سے قاصر۔'; +$PHPMAILER_LANG['invalid_address'] = 'بھیجنے سے قاصر: غلط ای میل پتہ:'; +$PHPMAILER_LANG['mailer_not_supported'] = ' میلر تعاون یافتہ نہیں ہے۔'; +$PHPMAILER_LANG['provide_address'] = 'آپ کو کم از کم ایک منزل کا ای میل پتہ فراہم کرنا چاہیے۔'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP خرابی: درج ذیل پتہ پر نہیں بھیجا جاسکا: '; +$PHPMAILER_LANG['signing'] = 'دستخط کی خرابی: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP ملنا ناکام ہوا'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP سرور کی خرابی: '; +$PHPMAILER_LANG['variable_set'] = 'متغیر سیٹ نہیں کیا جا سکا: '; +$PHPMAILER_LANG['extension_missing'] = 'ایکٹینشن موجود نہیں ہے۔ '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP سرور کوڈ: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'اضافی SMTP سرور کی معلومات:'; +$PHPMAILER_LANG['invalid_header'] = 'غلط ہیڈر کا نام یا قدر'; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php index 728a4994c..03d491165 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php @@ -9,11 +9,13 @@ */ $PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。'; +$PHPMAILER_LANG['buggy_php'] = '您的 PHP 版本存在漏洞,可能会导致消息损坏。为修复此问题,请切换到使用 SMTP 发送,在您的 php.ini 中禁用 mail.add_x_header 选项。切换到 MacOS 或 Linux,或将您的 PHP 升级到 7.0.17+ 或 7.1.3+ 版本。'; $PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; $PHPMAILER_LANG['empty_message'] = '邮件正文为空。'; $PHPMAILER_LANG['encoding'] = '未知编码:'; $PHPMAILER_LANG['execute'] = '无法执行:'; +$PHPMAILER_LANG['extension_missing'] = '缺少扩展名:'; $PHPMAILER_LANG['file_access'] = '无法访问文件:'; $PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; $PHPMAILER_LANG['from_failed'] = '发送地址错误:'; @@ -22,8 +24,13 @@ $PHPMAILER_LANG['invalid_address'] = '发送失败,电子邮箱地址是 $PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。'; $PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地址。'; $PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; -$PHPMAILER_LANG['signing'] = '登录失败:'; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。'; $PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错:'; $PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:'; -$PHPMAILER_LANG['extension_missing'] = '丢失模块 Extension:'; +$PHPMAILER_LANG['invalid_header'] = '无效的标题名称或值'; +$PHPMAILER_LANG['invalid_hostentry'] = '无效的hostentry: '; +$PHPMAILER_LANG['invalid_host'] = '无效的主机:'; +$PHPMAILER_LANG['signing'] = '签名错误:'; +$PHPMAILER_LANG['smtp_code'] = 'SMTP代码: '; +$PHPMAILER_LANG['smtp_code_ex'] = '附加SMTP信息: '; +$PHPMAILER_LANG['smtp_detail'] = '详情:'; diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/DSNConfigurator.php new file mode 100644 index 000000000..7058c1f05 --- /dev/null +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/DSNConfigurator.php @@ -0,0 +1,245 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2023 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Configure PHPMailer with DSN string. + * + * @see https://en.wikipedia.org/wiki/Data_source_name + * + * @author Oleg Voronkovich + */ +class DSNConfigurator +{ + /** + * Create new PHPMailer instance configured by DSN. + * + * @param string $dsn DSN + * @param bool $exceptions Should we throw external exceptions? + * + * @return PHPMailer + */ + public static function mailer($dsn, $exceptions = null) + { + static $configurator = null; + + if (null === $configurator) { + $configurator = new DSNConfigurator(); + } + + return $configurator->configure(new PHPMailer($exceptions), $dsn); + } + + /** + * Configure PHPMailer instance with DSN string. + * + * @param PHPMailer $mailer PHPMailer instance + * @param string $dsn DSN + * + * @return PHPMailer + */ + public function configure(PHPMailer $mailer, $dsn) + { + $config = $this->parseDSN($dsn); + + $this->applyConfig($mailer, $config); + + return $mailer; + } + + /** + * Parse DSN string. + * + * @param string $dsn DSN + * + * @throws Exception If DSN is malformed + * + * @return array Configuration + */ + private function parseDSN($dsn) + { + $config = $this->parseUrl($dsn); + + if (false === $config || !isset($config['scheme']) || !isset($config['host'])) { + throw new Exception('Malformed DSN'); + } + + if (isset($config['query'])) { + parse_str($config['query'], $config['query']); + } + + return $config; + } + + /** + * Apply configuration to mailer. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + * + * @throws Exception If scheme is invalid + */ + private function applyConfig(PHPMailer $mailer, $config) + { + switch ($config['scheme']) { + case 'mail': + $mailer->isMail(); + break; + case 'sendmail': + $mailer->isSendmail(); + break; + case 'qmail': + $mailer->isQmail(); + break; + case 'smtp': + case 'smtps': + $mailer->isSMTP(); + $this->configureSMTP($mailer, $config); + break; + default: + throw new Exception( + sprintf( + 'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".', + $config['scheme'] + ) + ); + } + + if (isset($config['query'])) { + $this->configureOptions($mailer, $config['query']); + } + } + + /** + * Configure SMTP. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + */ + private function configureSMTP($mailer, $config) + { + $isSMTPS = 'smtps' === $config['scheme']; + + if ($isSMTPS) { + $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } + + $mailer->Host = $config['host']; + + if (isset($config['port'])) { + $mailer->Port = $config['port']; + } elseif ($isSMTPS) { + $mailer->Port = SMTP::DEFAULT_SECURE_PORT; + } + + $mailer->SMTPAuth = isset($config['user']) || isset($config['pass']); + + if (isset($config['user'])) { + $mailer->Username = $config['user']; + } + + if (isset($config['pass'])) { + $mailer->Password = $config['pass']; + } + } + + /** + * Configure options. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $options Options + * + * @throws Exception If option is unknown + */ + private function configureOptions(PHPMailer $mailer, $options) + { + $allowedOptions = get_object_vars($mailer); + + unset($allowedOptions['Mailer']); + unset($allowedOptions['SMTPAuth']); + unset($allowedOptions['Username']); + unset($allowedOptions['Password']); + unset($allowedOptions['Hostname']); + unset($allowedOptions['Port']); + unset($allowedOptions['ErrorInfo']); + + $allowedOptions = \array_keys($allowedOptions); + + foreach ($options as $key => $value) { + if (!in_array($key, $allowedOptions)) { + throw new Exception( + sprintf( + 'Unknown option: "%s". Allowed values: "%s"', + $key, + implode('", "', $allowedOptions) + ) + ); + } + + switch ($key) { + case 'AllowEmpty': + case 'SMTPAutoTLS': + case 'SMTPKeepAlive': + case 'SingleTo': + case 'UseSendmailOptions': + case 'do_verp': + case 'DKIM_copyHeaderFields': + $mailer->$key = (bool) $value; + break; + case 'Priority': + case 'SMTPDebug': + case 'WordWrap': + $mailer->$key = (int) $value; + break; + default: + $mailer->$key = $value; + break; + } + } + } + + /** + * Parse a URL. + * Wrapper for the built-in parse_url function to work around a bug in PHP 5.5. + * + * @param string $url URL + * + * @return array|false + */ + protected function parseUrl($url) + { + if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) { + return parse_url($url); + } + + $chunks = explode('?', $url); + if (is_array($chunks)) { + $result = parse_url($chunks[0]); + if (is_array($result)) { + $result['query'] = $chunks[1]; + } + return $result; + } + + return false; + } +} diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/Exception.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/Exception.php index 52eaf9515..09c1a2cfe 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/Exception.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/Exception.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuth.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuth.php index c1d5b7762..a7e958860 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuth.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuth.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -29,7 +29,7 @@ use League\OAuth2\Client\Token\AccessToken; * OAuth - OAuth2 authentication wrapper class. * Uses the oauth2-client package from the League of Extraordinary Packages. * - * @see http://oauth2-client.thephpleague.com + * @see https://oauth2-client.thephpleague.com * * @author Marcus Bointon (Synchro/coolbru) */ diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php index 115550743..cbda1a129 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/PHPMailer.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/PHPMailer.php index 718216b54..4b3f34c88 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/PHPMailer.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -152,8 +152,7 @@ class PHPMailer * Only supported in simple alt or alt_inline message types * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. * - * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ - * @see http://kigkonsult.se/iCalcreator/ + * @see https://kigkonsult.se/iCalcreator/ * * @var string */ @@ -254,7 +253,7 @@ class PHPMailer * You can set your own, but it must be in the format "", * as defined in RFC5322 section 3.6.4 or it will be ignored. * - * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * @see https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4 * * @var string */ @@ -350,13 +349,20 @@ class PHPMailer public $Password = ''; /** - * SMTP auth type. - * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. * * @var string */ public $AuthType = ''; + /** + * SMTP SMTPXClient command attributes + * + * @var array + */ + protected $SMTPXClient = []; + /** * An implementation of the PHPMailer OAuthTokenProvider interface. * @@ -381,7 +387,7 @@ class PHPMailer * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual * delivery's outcome (success or failure) is not yet decided. * - * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + * @see https://www.rfc-editor.org/rfc/rfc3461.html#section-4.1 for more information about NOTIFY */ public $dsn = ''; @@ -461,7 +467,7 @@ class PHPMailer * Only applicable when sending via SMTP. * * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path - * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * @see https://www.postfix.org/VERP_README.html Postfix VERP info * * @var bool */ @@ -544,10 +550,10 @@ class PHPMailer * The function that handles the result of the send email action. * It is called out by send() for each email sent. * - * Value can be any php callable: http://www.php.net/is_callable + * Value can be any php callable: https://www.php.net/is_callable * * Parameters: - * bool $result result of the send action + * bool $result result of the send action * array $to email addresses of the recipients * array $cc cc email addresses * array $bcc bcc email addresses @@ -574,6 +580,10 @@ class PHPMailer * May be a callable to inject your own validator, but there are several built-in validators. * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. * + * If CharSet is UTF8, the validator is left at the default value, + * and you send to addresses that use non-ASCII local parts, then + * PHPMailer automatically changes to the 'eai' validator. + * * @see PHPMailer::validateAddress() * * @var string|callable @@ -653,6 +663,14 @@ class PHPMailer */ protected $ReplyToQueue = []; + /** + * Whether the need for SMTPUTF8 has been detected. Set by + * preSend() if necessary. + * + * @var bool + */ + public $UseSMTPUTF8 = false; + /** * The array of attachments. * @@ -750,7 +768,7 @@ class PHPMailer * * @var string */ - const VERSION = '6.6.0'; + const VERSION = '6.12.0'; /** * Error severity: message only, continue processing. @@ -795,7 +813,7 @@ class PHPMailer * The maximum line length supported by mail(). * * Background: mail() will sometimes corrupt messages - * with headers headers longer than 65 chars, see #818. + * with headers longer than 65 chars, see #818. * * @var int */ @@ -858,7 +876,7 @@ class PHPMailer private function mailPassthru($to, $subject, $body, $header, $params) { //Check overloading of mail function to avoid double-encoding - if (ini_get('mbstring.func_overload') & 1) { + if ((int)ini_get('mbstring.func_overload') & 1) { $subject = $this->secureHeader($subject); } else { $subject = $this->encodeHeader($this->secureHeader($subject)); @@ -896,7 +914,7 @@ class PHPMailer } //Is this a PSR-3 logger? if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { - $this->Debugoutput->debug($str); + $this->Debugoutput->debug(rtrim($str, "\r\n")); return; } @@ -1065,9 +1083,9 @@ class PHPMailer * be modified after calling this function), addition of such addresses is delayed until send(). * Addresses that have been added already return false, but do not throw exceptions. * - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name + * @param string $kind One of 'to', 'cc', 'bcc', or 'Reply-To' + * @param string $address The email address + * @param string $name An optional username associated with the address * * @throws Exception * @@ -1075,9 +1093,11 @@ class PHPMailer */ protected function addOrEnqueueAnAddress($kind, $address, $name) { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - $pos = strrpos($address, '@'); + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } if (false === $pos) { //At-sign is missing. $error_message = sprintf( @@ -1094,21 +1114,30 @@ class PHPMailer return false; } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } $params = [$kind, $address, $name]; //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. - if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { - if ('Reply-To' !== $kind) { - if (!array_key_exists($address, $this->RecipientsQueue)) { - $this->RecipientsQueue[$address] = $params; + //Domain is assumed to be whatever is after the last @ symbol in the address + if ($this->has8bitChars(substr($address, ++$pos))) { + if (static::idnSupported()) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; return true; } - } elseif (!array_key_exists($address, $this->ReplyToQueue)) { - $this->ReplyToQueue[$address] = $params; - - return true; } - + //We have an 8-bit domain, but we are missing the necessary extensions to support it + //Or we are already sending to this address return false; } @@ -1116,6 +1145,22 @@ class PHPMailer return call_user_func_array([$this, 'addAnAddress'], $params); } + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + /** * Add an address to one of the recipient arrays or to the ReplyTo array. * Addresses that have been added already return false, but do not throw exceptions. @@ -1130,6 +1175,15 @@ class PHPMailer */ protected function addAnAddress($kind, $address, $name = '') { + if ( + self::$validator === 'php' && + ((bool) preg_match('/[\x80-\xFF]/', $address)) + ) { + //The caller has not altered the validator and is sending to an address + //with UTF-8, so assume that they want UTF-8 support instead of failing + $this->CharSet = self::CHARSET_UTF8; + self::$validator = 'eai'; + } if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { $error_message = sprintf( '%s: %s', @@ -1181,7 +1235,7 @@ class PHPMailer * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. * Note that quotes in the name part are removed. * - * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation * * @param string $addrstr The address list string * @param bool $useimap Whether to use the IMAP extension to parse the list @@ -1280,7 +1334,7 @@ class PHPMailer */ public function setFrom($address, $name = '', $auto = true) { - $address = trim($address); + $address = trim((string)$address); $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim //Don't validate now addresses with IDN. Will be done in send(). $pos = strrpos($address, '@'); @@ -1332,6 +1386,7 @@ class PHPMailer * * `pcre` Use old PCRE implementation; * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `eai` Use a pattern similar to the HTML5 spec for 'email' and to firefox, extended to support EAI (RFC6530). * * `noregex` Don't use a regex: super fast, really dumb. * Alternatively you may pass in a callable to inject your own validator, for example: * @@ -1376,7 +1431,6 @@ class PHPMailer * * IPv6 literals: 'first.last@[IPv6:a1::]' * Not all of these will necessarily work for sending! * - * @see http://squiloople.com/2009/12/20/email-address-validation/ * @copyright 2009-2010 Michael Rushton * Feel free to use and redistribute this code. But please keep this copyright notice. */ @@ -1403,6 +1457,24 @@ class PHPMailer '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', $address ); + case 'eai': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type + * form input elements (as above), modified to accept Unicode email addresses. + * This is also more lenient than Firefox' html5 spec, in order to make the regex faster. + * 'eai' is an acronym for Email Address Internationalization. + * This validator is selected automatically if you attempt to use recipient addresses + * that contain Unicode characters in the local part. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + * @see https://en.wikipedia.org/wiki/International_email + */ + return (bool) preg_match( + '/^[-\p{L}\p{N}\p{M}.!#$%&\'*+\/=?^_`{|}~]+@[\p{L}\p{N}\p{M}](?:[\p{L}\p{N}\p{M}-]{0,61}' . + '[\p{L}\p{N}\p{M}])?(?:\.[\p{L}\p{N}\p{M}]' . + '(?:[-\p{L}\p{N}\p{M}]{0,61}[\p{L}\p{N}\p{M}])?)*$/usD', + $address + ); case 'php': default: return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; @@ -1536,9 +1608,26 @@ class PHPMailer $this->error_count = 0; //Reset errors $this->mailHeader = ''; + //The code below tries to support full use of Unicode, + //while remaining compatible with legacy SMTP servers to + //the greatest degree possible: If the message uses + //Unicode in the local parts of any addresses, it is sent + //using SMTPUTF8. If not, it it sent using + //punycode-encoded domains and plain SMTP. + if ( + static::CHARSET_UTF8 === strtolower($this->CharSet) && + ($this->anyAddressHasUnicodeLocalPart($this->RecipientsQueue) || + $this->anyAddressHasUnicodeLocalPart(array_keys($this->all_recipients)) || + $this->anyAddressHasUnicodeLocalPart($this->ReplyToQueue) || + $this->addressHasUnicodeLocalPart($this->From)) + ) { + $this->UseSMTPUTF8 = true; + } //Dequeue recipient and Reply-To addresses with IDN foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { - $params[1] = $this->punyencodeAddress($params[1]); + if (!$this->UseSMTPUTF8) { + $params[1] = $this->punyencodeAddress($params[1]); + } call_user_func_array([$this, 'addAnAddress'], $params); } if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { @@ -1547,17 +1636,21 @@ class PHPMailer //Validate From, Sender, and ConfirmReadingTo addresses foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { - $this->$address_kind = trim($this->$address_kind); - if (empty($this->$address_kind)) { + if ($this->{$address_kind} === null) { + $this->{$address_kind} = ''; continue; } - $this->$address_kind = $this->punyencodeAddress($this->$address_kind); - if (!static::validateAddress($this->$address_kind)) { + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { $error_message = sprintf( '%s (%s): %s', $this->lang('invalid_address'), $address_kind, - $this->$address_kind + $this->{$address_kind} ); $this->setError($error_message); $this->edebug($error_message); @@ -1657,17 +1750,17 @@ class PHPMailer default: $sendMethod = $this->Mailer . 'Send'; if (method_exists($this, $sendMethod)) { - return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); } return $this->mailSend($this->MIMEHeader, $this->MIMEBody); } } catch (Exception $exc) { - if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) { - $this->smtp->reset(); - } $this->setError($exc->getMessage()); $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } if ($this->exceptions) { throw $exc; } @@ -1699,9 +1792,8 @@ class PHPMailer //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //A space after `-f` is optional, but there is a long history of its presence //causing problems, so we don't use one - //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html - //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html - //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html //Example problem: https://www.drupal.org/node/1057954 //PHP 5.6 workaround @@ -1839,7 +1931,7 @@ class PHPMailer */ protected static function isPermittedPath($path) { - //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + //Matches scheme definition from https://www.rfc-editor.org/rfc/rfc3986#section-3.1 return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); } @@ -1855,7 +1947,7 @@ class PHPMailer if (!static::isPermittedPath($path)) { return false; } - $readable = file_exists($path); + $readable = is_file($path); //If not a UNC path (expected to start with \\), check read permission, see #2069 if (strpos($path, '\\\\') !== 0) { $readable = $readable && is_readable($path); @@ -1866,7 +1958,7 @@ class PHPMailer /** * Send mail using the PHP mail() function. * - * @see http://www.php.net/manual/en/book.mail.php + * @see https://www.php.net/manual/en/book.mail.php * * @param string $header The message headers * @param string $body The message body @@ -1883,15 +1975,21 @@ class PHPMailer foreach ($this->to as $toaddr) { $toArr[] = $this->addrFormat($toaddr); } - $to = implode(', ', $toArr); + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } $params = null; //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //A space after `-f` is optional, but there is a long history of its presence //causing problems, so we don't use one - //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html - //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html - //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html //Example problem: https://www.drupal.org/node/1057954 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. @@ -1966,6 +2064,38 @@ class PHPMailer return $this->smtp; } + /** + * Provide SMTP XCLIENT attributes + * + * @param string $name Attribute name + * @param ?string $value Attribute value + * + * @return bool + */ + public function setSMTPXclientAttribute($name, $value) + { + if (!in_array($name, SMTP::$xclient_allowed_attributes)) { + return false; + } + if (isset($this->SMTPXClient[$name]) && $value === null) { + unset($this->SMTPXClient[$name]); + } elseif ($value !== null) { + $this->SMTPXClient[$name] = $value; + } + + return true; + } + + /** + * Get SMTP XCLIENT attributes + * + * @return array + */ + public function getSMTPXclientAttributes() + { + return $this->SMTPXClient; + } + /** * Send mail via SMTP. * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. @@ -1988,12 +2118,20 @@ class PHPMailer if (!$this->smtpConnect($this->SMTPOptions)) { throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); } + //If we have recipient addresses that need Unicode support, + //but the server doesn't support it, stop here + if ($this->UseSMTPUTF8 && !$this->smtp->getServerExt('SMTPUTF8')) { + throw new Exception($this->lang('no_smtputf8'), self::STOP_CRITICAL); + } //Sender already validated in preSend() if ('' === $this->Sender) { $smtp_from = $this->From; } else { $smtp_from = $this->Sender; } + if (count($this->SMTPXClient)) { + $this->smtp->xclient($this->SMTPXClient); + } if (!$this->smtp->mail($smtp_from)) { $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); @@ -2086,6 +2224,10 @@ class PHPMailer $this->smtp->setDebugLevel($this->SMTPDebug); $this->smtp->setDebugOutput($this->Debugoutput); $this->smtp->setVerp($this->do_verp); + $this->smtp->setSMTPUTF8($this->UseSMTPUTF8); + if ($this->Host === null) { + $this->Host = 'localhost'; + } $hosts = explode(';', $this->Host); $lastexception = null; @@ -2153,10 +2295,17 @@ class PHPMailer $this->smtp->hello($hello); //Automatically enable TLS encryption if: //* it's not disabled + //* we are not connecting to localhost //* we have openssl extension //* we are not already using SSL //* the server offers STARTTLS - if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + if ( + $this->SMTPAutoTLS && + $this->Host !== 'localhost' && + $sslext && + $secure !== 'ssl' && + $this->smtp->getServerExt('STARTTLS') + ) { $tls = true; } if ($tls) { @@ -2192,7 +2341,8 @@ class PHPMailer //As we've caught all exceptions, just report whatever the last one was if ($this->exceptions && null !== $lastexception) { throw $lastexception; - } elseif ($this->exceptions) { + } + if ($this->exceptions) { // no exception was thrown, likely $this->smtp->connect() failed $message = $this->getSmtpErrorMessage('connect_host'); throw new Exception($message); @@ -2272,6 +2422,7 @@ class PHPMailer 'smtp_detail' => 'Detail: ', 'smtp_error' => 'SMTP server error: ', 'variable_set' => 'Cannot set or reset variable: ', + 'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses', ]; if (empty($lang_path)) { //Calculate an absolute path so it can work if CWD is not here @@ -2388,7 +2539,7 @@ class PHPMailer */ public function addrFormat($addr) { - if (empty($addr[1])) { //No name provided + if (!isset($addr[1]) || ($addr[1] === '')) { //No name provided return $this->secureHeader($addr[0]); } @@ -2621,7 +2772,7 @@ class PHPMailer } //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 - //https://tools.ietf.org/html/rfc5322#section-3.6.4 + //https://www.rfc-editor.org/rfc/rfc5322#section-3.6.4 if ( '' !== $this->MessageID && preg_match( @@ -2775,10 +2926,7 @@ class PHPMailer { $body = ''; //Create unique IDs and preset boundaries - $this->uniqueid = $this->generateId(); - $this->boundary[1] = 'b1_' . $this->uniqueid; - $this->boundary[2] = 'b2_' . $this->uniqueid; - $this->boundary[3] = 'b3_' . $this->uniqueid; + $this->setBoundaries(); if ($this->sign_key_file) { $body .= $this->getMailMIME() . static::$LE; @@ -2789,7 +2937,9 @@ class PHPMailer $bodyEncoding = $this->Encoding; $bodyCharSet = $this->CharSet; //Can we do a 7-bit downgrade? - if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) { + if ($this->UseSMTPUTF8) { + $bodyEncoding = static::ENCODING_8BIT; + } elseif (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) { $bodyEncoding = static::ENCODING_7BIT; //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit $bodyCharSet = static::CHARSET_ASCII; @@ -2814,7 +2964,7 @@ class PHPMailer $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE; } //Use this as a preamble in all multipart message types - $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE; + $mimepre = ''; switch ($this->message_type) { case 'inline': $body .= $mimepre; @@ -3050,6 +3200,18 @@ class PHPMailer return $body; } + /** + * Get the boundaries that this message will use + * @return array + */ + public function getBoundaries() + { + if (empty($this->boundary)) { + $this->setBoundaries(); + } + return $this->boundary; + } + /** * Return the start of a message boundary. * @@ -3414,7 +3576,8 @@ class PHPMailer /** * Encode a header value (not including its label) optimally. * Picks shortest of Q, B, or none. Result includes folding if needed. - * See RFC822 definitions for phrase, comment and text positions. + * See RFC822 definitions for phrase, comment and text positions, + * and RFC2047 for inline encodings. * * @param string $str The header value to encode * @param string $position What context the string will be used in @@ -3423,6 +3586,11 @@ class PHPMailer */ public function encodeHeader($str, $position = 'text') { + $position = strtolower($position); + if ($this->UseSMTPUTF8 && !("comment" === $position)) { + return trim(static::normalizeBreaks($str)); + } + $matchcount = 0; switch (strtolower($position)) { case 'phrase': @@ -3537,7 +3705,7 @@ class PHPMailer * without breaking lines within a character. * Adapted from a function by paravoid. * - * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * @see https://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 * * @param string $str multi-byte text to wrap encode * @param string $linebreak string to use as linefeed/end-of-line @@ -3593,7 +3761,7 @@ class PHPMailer /** * Encode a string using Q encoding. * - * @see http://tools.ietf.org/html/rfc2047#section-4.2 + * @see https://www.rfc-editor.org/rfc/rfc2047#section-4.2 * * @param string $str the text to encode * @param string $position Where the text is going to be used, see the RFC for what that means @@ -3705,20 +3873,21 @@ class PHPMailer * These differ from 'regular' attachments in that they are intended to be * displayed inline with the message, not just attached for download. * This is used in HTML messages that embed the images - * the HTML refers to using the $cid value. + * the HTML refers to using the `$cid` value in `img` tags, for example ``. * Never use a user-supplied path to a file! * * @param string $path Path to the attachment * @param string $cid Content ID of the attachment; Use this to reference * the content when using an embedded image in HTML - * @param string $name Overrides the attachment name - * @param string $encoding File encoding (see $Encoding) - * @param string $type File MIME type - * @param string $disposition Disposition to use - * - * @throws Exception + * @param string $name Overrides the attachment filename + * @param string $encoding File encoding (see $Encoding) defaults to `base64` + * @param string $type File MIME type (by default mapped from the `$path` filename's extension) + * @param string $disposition Disposition to use: `inline` (default) or `attachment` + * (unlikely you want this – {@see `addAttachment()`} instead) * * @return bool True on successfully adding an attachment + * @throws Exception + * */ public function addEmbeddedImage( $path, @@ -4002,6 +4171,79 @@ class PHPMailer $this->CustomHeader = []; } + /** + * Clear a specific custom header by name or name and value. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + * + * @return bool True if a header was replaced successfully + */ + public function clearCustomHeader($name, $value = null) + { + if (null === $value && strpos($name, ':') !== false) { + //Value passed in as name:value + list($name, $value) = explode(':', $name, 2); + } + $name = trim($name); + $value = (null === $value) ? null : trim($value); + + foreach ($this->CustomHeader as $k => $pair) { + if ($pair[0] == $name) { + // We remove the header if the value is not provided or it matches. + if (null === $value || $pair[1] == $value) { + unset($this->CustomHeader[$k]); + } + } + } + + return true; + } + + /** + * Replace a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + * + * @return bool True if a header was replaced successfully + * @throws Exception + */ + public function replaceCustomHeader($name, $value = null) + { + if (null === $value && strpos($name, ':') !== false) { + //Value passed in as name:value + list($name, $value) = explode(':', $name, 2); + } + $name = trim($name); + $value = (null === $value) ? '' : trim($value); + + $replaced = false; + foreach ($this->CustomHeader as $k => $pair) { + if ($pair[0] == $name) { + if ($replaced) { + unset($this->CustomHeader[$k]); + continue; + } + if (strpbrk($name . $value, "\r\n") !== false) { + if ($this->exceptions) { + throw new Exception($this->lang('invalid_header')); + } + + return false; + } + $this->CustomHeader[$k] = [$name, $value]; + $replaced = true; + } + } + + return true; + } + /** * Add an error message to the error container. * @@ -4013,7 +4255,7 @@ class PHPMailer if ('smtp' === $this->Mailer && null !== $this->smtp) { $lasterror = $this->smtp->getError(); if (!empty($lasterror['error'])) { - $msg .= $this->lang('smtp_error') . $lasterror['error']; + $msg .= ' ' . $this->lang('smtp_error') . $lasterror['error']; if (!empty($lasterror['detail'])) { $msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail']; } @@ -4057,7 +4299,7 @@ class PHPMailer $result = $_SERVER['SERVER_NAME']; } elseif (function_exists('gethostname') && gethostname() !== false) { $result = gethostname(); - } elseif (php_uname('n') !== false) { + } elseif (php_uname('n') !== '') { $result = php_uname('n'); } if (!static::isValidHost($result)) { @@ -4082,7 +4324,7 @@ class PHPMailer empty($host) || !is_string($host) || strlen($host) > 256 - || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+\])$/', $host) + || !preg_match('/^([a-z\d.-]*|\[[a-f\d:]+\])$/i', $host) ) { return false; } @@ -4096,14 +4338,49 @@ class PHPMailer //Is it a valid IPv4 address? return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; } - if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) { - //Is it a syntactically valid hostname? - return true; - } + //Is it a syntactically valid hostname (when embedded in a URL)? + return filter_var('https://' . $host, FILTER_VALIDATE_URL) !== false; + } + /** + * Check whether the supplied address uses Unicode in the local part. + * + * @return bool + */ + protected function addressHasUnicodeLocalPart($address) + { + return (bool) preg_match('/[\x80-\xFF].*@/', $address); + } + + /** + * Check whether any of the supplied addresses use Unicode in the local part. + * + * @return bool + */ + protected function anyAddressHasUnicodeLocalPart($addresses) + { + foreach ($addresses as $address) { + if (is_array($address)) { + $address = $address[0]; + } + if ($this->addressHasUnicodeLocalPart($address)) { + return true; + } + } return false; } + /** + * Check whether the message requires SMTPUTF8 based on what's known so far. + * + * @return bool + */ + public function needsSMTPUTF8() + { + return $this->UseSMTPUTF8; + } + + /** * Get an error message in the current language. * @@ -4170,6 +4447,7 @@ class PHPMailer * @param string $name Custom header name * @param string|null $value Header value * + * @return bool True if a header was set successfully * @throws Exception */ public function addCustomHeader($name, $value = null) @@ -4464,6 +4742,7 @@ class PHPMailer 'ics' => 'text/calendar', 'xml' => 'text/xml', 'xsl' => 'text/xml', + 'csv' => 'text/csv', 'wmv' => 'video/x-ms-wmv', 'mpeg' => 'video/mpeg', 'mpe' => 'video/mpeg', @@ -4510,7 +4789,7 @@ class PHPMailer * Multi-byte-safe pathinfo replacement. * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe. * - * @see http://www.php.net/manual/en/function.pathinfo.php#107461 + * @see https://www.php.net/manual/en/function.pathinfo.php#107461 * * @param string $path A filename or path, does not need to exist as a file * @param int|string $options Either a PATHINFO_* constant, @@ -4571,7 +4850,7 @@ class PHPMailer public function set($name, $value = '') { if (property_exists($this, $name)) { - $this->$name = $value; + $this->{$name} = $value; return true; } @@ -4618,15 +4897,27 @@ class PHPMailer } /** - * Remove trailing breaks from a string. + * Remove trailing whitespace from a string. + * + * @param string $text + * + * @return string The text to remove whitespace from + */ + public static function stripTrailingWSP($text) + { + return rtrim($text, " \r\n\t"); + } + + /** + * Strip trailing line breaks from a string. * * @param string $text * * @return string The text to remove breaks from */ - public static function stripTrailingWSP($text) + public static function stripTrailingBreaks($text) { - return rtrim($text, " \r\n\t"); + return rtrim($text, "\r\n"); } /** @@ -4733,7 +5024,7 @@ class PHPMailer * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. * Canonicalized headers should *always* use CRLF, regardless of mailer setting. * - * @see https://tools.ietf.org/html/rfc6376#section-3.4.2 + * @see https://www.rfc-editor.org/rfc/rfc6376#section-3.4.2 * * @param string $signHeader Header * @@ -4745,7 +5036,7 @@ class PHPMailer $signHeader = static::normalizeBreaks($signHeader, self::CRLF); //Unfold header lines //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` - //@see https://tools.ietf.org/html/rfc5322#section-2.2 + //@see https://www.rfc-editor.org/rfc/rfc5322#section-2.2 //That means this may break if you do something daft like put vertical tabs in your headers. $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); //Break headers out into an array @@ -4777,7 +5068,7 @@ class PHPMailer * Uses the 'simple' algorithm from RFC6376 section 3.4.3. * Canonicalized bodies should *always* use CRLF, regardless of mailer setting. * - * @see https://tools.ietf.org/html/rfc6376#section-3.4.3 + * @see https://www.rfc-editor.org/rfc/rfc6376#section-3.4.3 * * @param string $body Message Body * @@ -4792,7 +5083,7 @@ class PHPMailer $body = static::normalizeBreaks($body, self::CRLF); //Reduce multiple trailing line breaks to a single one - return static::stripTrailingWSP($body) . self::CRLF; + return static::stripTrailingBreaks($body) . self::CRLF; } /** @@ -4813,7 +5104,7 @@ class PHPMailer $DKIMquery = 'dns/txt'; //Query method $DKIMtime = time(); //Always sign these headers without being asked - //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1 + //Recommended list from https://www.rfc-editor.org/rfc/rfc6376#section-5.4.1 $autoSignHeaders = [ 'from', 'to', @@ -4919,7 +5210,7 @@ class PHPMailer } //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag //which is appended after calculating the signature - //https://tools.ietf.org/html/rfc6376#section-3.5 + //https://www.rfc-editor.org/rfc/rfc6376#section-3.5 $dkimSignatureHeader = 'DKIM-Signature: v=1;' . ' d=' . $this->DKIM_domain . ';' . ' s=' . $this->DKIM_selector . ';' . static::$LE . diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/POP3.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/POP3.php index 86cfebd20..4d3e8db2b 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/POP3.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/POP3.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -46,7 +46,7 @@ class POP3 * * @var string */ - const VERSION = '6.6.0'; + const VERSION = '6.12.0'; /** * Default POP3 port number. @@ -250,7 +250,9 @@ class POP3 //On Windows this will raise a PHP Warning error if the hostname doesn't exist. //Rather than suppress it with @fsockopen, capture it cleanly instead - set_error_handler([$this, 'catchWarning']); + set_error_handler(function () { + call_user_func_array([$this, 'catchWarning'], func_get_args()); + }); if (false === $port) { $port = static::DEFAULT_PORT; @@ -337,7 +339,12 @@ class POP3 */ public function disconnect() { - $this->sendString('QUIT'); + // If could not connect at all, no need to disconnect + if ($this->pop_conn === false) { + return; + } + + $this->sendString('QUIT' . static::LE); // RFC 1939 shows POP3 server sending a +OK response to the QUIT command. // Try to get it. Ignore any failures here. diff --git a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/SMTP.php b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/SMTP.php index 5ecad21da..d8749c5ba 100644 --- a/data/web/inc/lib/vendor/phpmailer/phpmailer/src/SMTP.php +++ b/data/web/inc/lib/vendor/phpmailer/phpmailer/src/SMTP.php @@ -13,7 +13,7 @@ * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. @@ -35,7 +35,7 @@ class SMTP * * @var string */ - const VERSION = '6.6.0'; + const VERSION = '6.12.0'; /** * SMTP line break constant. @@ -51,11 +51,18 @@ class SMTP */ const DEFAULT_PORT = 25; + /** + * The SMTPs port to use if one is not specified. + * + * @var int + */ + const DEFAULT_SECURE_PORT = 465; + /** * The maximum line length allowed by RFC 5321 section 4.5.3.1.6, * *excluding* a trailing CRLF break. * - * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6 + * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.6 * * @var int */ @@ -65,7 +72,7 @@ class SMTP * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5, * *including* a trailing CRLF line break. * - * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5 + * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.5 * * @var int */ @@ -145,19 +152,28 @@ class SMTP /** * Whether to use VERP. * - * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path - * @see http://www.postfix.org/VERP_README.html Info on VERP + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see https://www.postfix.org/VERP_README.html Info on VERP * * @var bool */ public $do_verp = false; + /** + * Whether to use SMTPUTF8. + * + * @see https://www.rfc-editor.org/rfc/rfc6531 + * + * @var bool + */ + public $do_smtputf8 = false; + /** * The timeout value for connection, in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. * - * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2 * * @var int */ @@ -180,16 +196,29 @@ class SMTP */ protected $smtp_transaction_id_patterns = [ 'exim' => '/[\d]{3} OK id=(.*)/', - 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', - 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', - 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', + 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/', + 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/', + 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/', 'Amazon_SES' => '/[\d]{3} Ok (.*)/', 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', - 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', + 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/', 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/', + 'ZoneMTA' => '/[\d]{3} Message queued as (.*)/', 'Mailjet' => '/[\d]{3} OK queued as (.*)/', ]; + /** + * Allowed SMTP XCLIENT attributes. + * Must be allowed by the SMTP server. EHLO response is not checked. + * + * @see https://www.postfix.org/XCLIENT_README.html + * + * @var array + */ + public static $xclient_allowed_attributes = [ + 'NAME', 'ADDR', 'PORT', 'PROTO', 'HELO', 'LOGIN', 'DESTADDR', 'DESTPORT' + ]; + /** * The last transaction ID issued in response to a DATA command, * if one was detected. @@ -260,7 +289,8 @@ class SMTP } //Is this a PSR-3 logger? if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { - $this->Debugoutput->debug($str); + //Remove trailing line breaks potentially added by calls to SMTP::client_send() + $this->Debugoutput->debug(rtrim($str, "\r\n")); return; } @@ -273,6 +303,7 @@ class SMTP switch ($this->Debugoutput) { case 'error_log': //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ error_log($str); break; case 'html': @@ -351,7 +382,7 @@ class SMTP } //Anything other than a 220 response means something went wrong //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error - //https://tools.ietf.org/html/rfc5321#section-3.1 + //https://www.rfc-editor.org/rfc/rfc5321#section-3.1 if ($responseCode === 554) { $this->quit(); } @@ -384,7 +415,9 @@ class SMTP $errstr = ''; if ($streamok) { $socket_context = stream_context_create($options); - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $connection = stream_socket_client( $host . ':' . $port, $errno, @@ -399,7 +432,9 @@ class SMTP 'Connection: stream_socket_client not available, falling back to fsockopen', self::DEBUG_CONNECTION ); - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $connection = fsockopen( $host, $port, @@ -463,7 +498,9 @@ class SMTP } //Begin encrypted connection - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $crypto_ok = stream_socket_enable_crypto( $this->smtp_conn, true, @@ -554,7 +591,7 @@ class SMTP } //Send encoded username and password if ( - //Format from https://tools.ietf.org/html/rfc4616#section-2 + //Format from https://www.rfc-editor.org/rfc/rfc4616#section-2 //We skip the first field (it's forgery), so the string starts with a null byte !$this->sendCommand( 'User & Password', @@ -628,7 +665,7 @@ class SMTP } //The following borrowed from - //http://php.net/manual/en/function.mhash.php#27225 + //https://www.php.net/manual/en/function.mhash.php#27225 //RFC 2104 HMAC implementation for php. //Creates an md5 HMAC. @@ -682,7 +719,6 @@ class SMTP */ public function close() { - $this->setError(''); $this->server_caps = null; $this->helo_rply = null; if (is_resource($this->smtp_conn)) { @@ -697,7 +733,7 @@ class SMTP * Send an SMTP DATA command. * Issues a data command and sends the msg_data to the server, * finalizing the mail transaction. $msg_data is the message - * that is to be send with the headers. Each header needs to be + * that is to be sent with the headers. Each header needs to be * on a single line followed by a with the message headers * and the message body being separated by an additional . * Implements RFC 821: DATA . @@ -725,7 +761,7 @@ class SMTP $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field - * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * of the first line (':' separated) does not contain a space then it _should_ be a header, and we will * process all lines before a blank line as headers. */ @@ -768,7 +804,7 @@ class SMTP //Send the lines to the server foreach ($lines_out as $line_out) { //Dot-stuffing as per RFC5321 section 4.5.2 - //https://tools.ietf.org/html/rfc5321#section-4.5.2 + //https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2 if (!empty($line_out) && $line_out[0] === '.') { $line_out = '.' . $line_out; } @@ -886,7 +922,15 @@ class SMTP * $from. Returns true if successful or false otherwise. If True * the mail transaction is started and then one or more recipient * commands may be called followed by a data command. - * Implements RFC 821: MAIL FROM: . + * Implements RFC 821: MAIL FROM: and + * two extensions, namely XVERP and SMTPUTF8. + * + * The server's EHLO response is not checked. If use of either + * extensions is enabled even though the server does not support + * that, mail submission will fail. + * + * XVERP is documented at https://www.postfix.org/VERP_README.html + * and SMTPUTF8 is specified in RFC 6531. * * @param string $from Source address of this message * @@ -895,10 +939,11 @@ class SMTP public function mail($from) { $useVerp = ($this->do_verp ? ' XVERP' : ''); + $useSmtputf8 = ($this->do_smtputf8 ? ' SMTPUTF8' : ''); return $this->sendCommand( 'MAIL FROM', - 'MAIL FROM:<' . $from . '>' . $useVerp, + 'MAIL FROM:<' . $from . '>' . $useSmtputf8 . $useVerp, 250 ); } @@ -964,6 +1009,25 @@ class SMTP ); } + /** + * Send SMTP XCLIENT command to server and check its return code. + * + * @return bool True on success + */ + public function xclient(array $vars) + { + $xclient_options = ""; + foreach ($vars as $key => $value) { + if (in_array($key, SMTP::$xclient_allowed_attributes)) { + $xclient_options .= " {$key}={$value}"; + } + } + if (!$xclient_options) { + return true; + } + return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 250); + } + /** * Send an SMTP RSET command. * Abort any transaction that is currently in progress. @@ -1037,7 +1101,10 @@ class SMTP return false; } - $this->setError(''); + //Don't clear the error store when using keepalive + if ($command !== 'RSET') { + $this->setError(''); + } return true; } @@ -1121,7 +1188,9 @@ class SMTP } else { $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); } - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $result = fwrite($this->smtp_conn, $data); restore_error_handler(); @@ -1224,7 +1293,9 @@ class SMTP while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { //Must pass vars in here as params are by reference //solution for signals inspired by https://github.com/symfony/symfony/pull/6540 - set_error_handler([$this, 'errorHandler']); + set_error_handler(function () { + call_user_func_array([$this, 'errorHandler'], func_get_args()); + }); $n = stream_select($selR, $selW, $selW, $this->Timelimit); restore_error_handler(); @@ -1311,6 +1382,26 @@ class SMTP return $this->do_verp; } + /** + * Enable or disable use of SMTPUTF8. + * + * @param bool $enabled + */ + public function setSMTPUTF8($enabled = false) + { + $this->do_smtputf8 = $enabled; + } + + /** + * Get SMTPUTF8 use. + * + * @return bool + */ + public function getSMTPUTF8() + { + return $this->do_smtputf8; + } + /** * Set error messages and codes. * diff --git a/data/web/inc/lib/vendor/psr/http-client/CHANGELOG.md b/data/web/inc/lib/vendor/psr/http-client/CHANGELOG.md index e2dc25f51..babba7c7b 100644 --- a/data/web/inc/lib/vendor/psr/http-client/CHANGELOG.md +++ b/data/web/inc/lib/vendor/psr/http-client/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 1.0.3 + +Add `source` link in composer.json. No code changes. + +## 1.0.2 + +Allow PSR-7 (psr/http-message) 2.0. No code changes. + ## 1.0.1 Allow installation with PHP 8. No code changes. diff --git a/data/web/inc/lib/vendor/psr/http-client/README.md b/data/web/inc/lib/vendor/psr/http-client/README.md index 6876b8403..84af5c55d 100644 --- a/data/web/inc/lib/vendor/psr/http-client/README.md +++ b/data/web/inc/lib/vendor/psr/http-client/README.md @@ -7,6 +7,6 @@ Note that this is not a HTTP Client implementation of its own. It is merely abst The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. -[psr-url]: http://www.php-fig.org/psr/psr-18 +[psr-url]: https://www.php-fig.org/psr/psr-18 [package-url]: https://packagist.org/packages/psr/http-client [implementation-url]: https://packagist.org/providers/psr/http-client-implementation diff --git a/data/web/inc/lib/vendor/psr/http-client/composer.json b/data/web/inc/lib/vendor/psr/http-client/composer.json index c195f8ff1..6fed350be 100644 --- a/data/web/inc/lib/vendor/psr/http-client/composer.json +++ b/data/web/inc/lib/vendor/psr/http-client/composer.json @@ -7,12 +7,15 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "autoload": { "psr-4": { diff --git a/data/web/inc/lib/vendor/psr/http-factory/.gitignore b/data/web/inc/lib/vendor/psr/http-factory/.gitignore deleted file mode 100644 index d8a7996ab..000000000 --- a/data/web/inc/lib/vendor/psr/http-factory/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -composer.lock -vendor/ diff --git a/data/web/inc/lib/vendor/psr/http-factory/.pullapprove.yml b/data/web/inc/lib/vendor/psr/http-factory/.pullapprove.yml deleted file mode 100644 index 8cf081942..000000000 --- a/data/web/inc/lib/vendor/psr/http-factory/.pullapprove.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: default -reviewers: - - - name: contributors - required: 1 - teams: - - http-factory-contributors diff --git a/data/web/inc/lib/vendor/psr/http-factory/README.md b/data/web/inc/lib/vendor/psr/http-factory/README.md index 41d362a62..bf8913b57 100644 --- a/data/web/inc/lib/vendor/psr/http-factory/README.md +++ b/data/web/inc/lib/vendor/psr/http-factory/README.md @@ -1,10 +1,12 @@ HTTP Factories ============== -This repository holds all interfaces related to [PSR-17 (HTTP Message Factories)][psr-17]. -Please refer to the specification for a description. +This repository holds all interfaces related to [PSR-17 (HTTP Factories)][psr-url]. -You can find implementations of the specification by looking for packages providing the -[psr/http-factory-implementation](https://packagist.org/providers/psr/http-factory-implementation) virtual package. +Note that this is not a HTTP Factory implementation of its own. It is merely interfaces that describe the components of a HTTP Factory. -[psr-17]: https://www.php-fig.org/psr/psr-17/ +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-17/ +[package-url]: https://packagist.org/packages/psr/http-factory +[implementation-url]: https://packagist.org/providers/psr/http-factory-implementation diff --git a/data/web/inc/lib/vendor/psr/http-factory/composer.json b/data/web/inc/lib/vendor/psr/http-factory/composer.json index af62b290f..82a1d3266 100644 --- a/data/web/inc/lib/vendor/psr/http-factory/composer.json +++ b/data/web/inc/lib/vendor/psr/http-factory/composer.json @@ -1,6 +1,6 @@ { "name": "psr/http-factory", - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "psr", "psr-7", @@ -15,12 +15,15 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "autoload": { "psr-4": { diff --git a/data/web/inc/lib/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php b/data/web/inc/lib/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php index 7db4e30af..d7adbf0e2 100644 --- a/data/web/inc/lib/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php +++ b/data/web/inc/lib/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php @@ -15,10 +15,10 @@ interface UploadedFileFactoryInterface * * @param StreamInterface $stream Underlying stream representing the * uploaded file content. - * @param int $size in bytes + * @param int|null $size in bytes * @param int $error PHP file upload error - * @param string $clientFilename Filename as provided by the client, if any. - * @param string $clientMediaType Media type as provided by the client, if any. + * @param string|null $clientFilename Filename as provided by the client, if any. + * @param string|null $clientMediaType Media type as provided by the client, if any. * * @return UploadedFileInterface * @@ -26,9 +26,9 @@ interface UploadedFileFactoryInterface */ public function createUploadedFile( StreamInterface $stream, - int $size = null, + ?int $size = null, int $error = \UPLOAD_ERR_OK, - string $clientFilename = null, - string $clientMediaType = null + ?string $clientFilename = null, + ?string $clientMediaType = null ): UploadedFileInterface; } diff --git a/data/web/inc/lib/vendor/psr/http-message/README.md b/data/web/inc/lib/vendor/psr/http-message/README.md index 28185338f..2668be6c3 100644 --- a/data/web/inc/lib/vendor/psr/http-message/README.md +++ b/data/web/inc/lib/vendor/psr/http-message/README.md @@ -10,4 +10,7 @@ interface that describes a HTTP message. See the specification for more details. Usage ----- -We'll certainly need some stuff in here. \ No newline at end of file +Before reading the usage guide we recommend reading the PSR-7 interfaces method list: + +* [`PSR-7 Interfaces Method List`](docs/PSR7-Interfaces.md) +* [`PSR-7 Usage Guide`](docs/PSR7-Usage.md) \ No newline at end of file diff --git a/data/web/inc/lib/vendor/psr/http-message/composer.json b/data/web/inc/lib/vendor/psr/http-message/composer.json index b0d2937a0..c66e5aba4 100644 --- a/data/web/inc/lib/vendor/psr/http-message/composer.json +++ b/data/web/inc/lib/vendor/psr/http-message/composer.json @@ -7,11 +7,11 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "autoload": { "psr-4": { @@ -20,7 +20,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } } } diff --git a/data/web/inc/lib/vendor/psr/http-message/docs/PSR7-Interfaces.md b/data/web/inc/lib/vendor/psr/http-message/docs/PSR7-Interfaces.md new file mode 100644 index 000000000..3a7e7dda6 --- /dev/null +++ b/data/web/inc/lib/vendor/psr/http-message/docs/PSR7-Interfaces.md @@ -0,0 +1,130 @@ +# Interfaces + +The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces. + +The interfaces defined in PSR-7 are the following: + +| Class Name | Description | +|---|---| +| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message | +| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. | +| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. | +| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. | +| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream | +| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. | +| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. | + +## `Psr\Http\Message\MessageInterface` Methods + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `getProtocolVersion()` | Retrieve HTTP protocol version | 1.0 or 1.1 | +| `withProtocolVersion($version)` | Returns new message instance with given HTTP protocol version | | +| `getHeaders()` | Retrieve all HTTP Headers | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) | +| `hasHeader($name)` | Checks if HTTP Header with given name exists | | +| `getHeader($name)` | Retrieves a array with the values for a single header | | +| `getHeaderLine($name)` | Retrieves a comma-separated string of the values for a single header | | +| `withHeader($name, $value)` | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. | +| `withAddedHeader($name, $value)` | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created | +| `withoutHeader($name)` | Removes HTTP Header with given name| | +| `getBody()` | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`| +| `withBody(StreamInterface $body)` | Returns new message instance with given HTTP Message Body | | + + +## `Psr\Http\Message\RequestInterface` Methods + +Same methods as `Psr\Http\Message\MessageInterface` + the following methods: + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `getRequestTarget()` | Retrieves the message's request target | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) | +| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target | | +| `getMethod()` | Retrieves the HTTP method of the request. | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) | +| `withMethod($method)` | Returns a new message instance with the provided HTTP method | | +| `getUri()` | Retrieves the URI instance | | +| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI | | + + +## `Psr\Http\Message\ServerRequestInterface` Methods + +Same methods as `Psr\Http\Message\RequestInterface` + the following methods: + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `getServerParams() ` | Retrieve server parameters | Typically derived from `$_SERVER` | +| `getCookieParams()` | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` | +| `withCookieParams(array $cookies)` | Returns a new request instance with the specified cookies | | +| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments | | +| `getUploadedFiles()` | Retrieve normalized file upload data | | +| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files | | +| `getParsedBody()` | Retrieve any parameters provided in the request body | | +| `withParsedBody($data)` | Returns a new request instance with the specified body parameters | | +| `getAttributes()` | Retrieve attributes derived from the request | | +| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute | | +| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute | | +| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute | | + +## `Psr\Http\Message\ResponseInterface` Methods: + +Same methods as `Psr\Http\Message\MessageInterface` + the following methods: + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `getStatusCode()` | Gets the response status code. | | +| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | | +| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | | + +## `Psr\Http\Message\StreamInterface` Methods + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | | +| `close()` | Closes the stream and any underlying resources. | | +| `detach()` | Separates any underlying resources from the stream. | | +| `getSize()` | Get the size of the stream if known. | | +| `eof()` | Returns true if the stream is at the end of the stream.| | +| `isSeekable()` | Returns whether or not the stream is seekable. | | +| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | | +| `rewind()` | Seek to the beginning of the stream. | | +| `isWritable()` | Returns whether or not the stream is writable. | | +| `write($string)` | Write data to the stream. | | +| `isReadable()` | Returns whether or not the stream is readable. | | +| `read($length)` | Read data from the stream. | | +| `getContents()` | Returns the remaining contents in a string | | +| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | | + +## `Psr\Http\Message\UriInterface` Methods + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `getScheme()` | Retrieve the scheme component of the URI. | | +| `getAuthority()` | Retrieve the authority component of the URI. | | +| `getUserInfo()` | Retrieve the user information component of the URI. | | +| `getHost()` | Retrieve the host component of the URI. | | +| `getPort()` | Retrieve the port component of the URI. | | +| `getPath()` | Retrieve the path component of the URI. | | +| `getQuery()` | Retrieve the query string of the URI. | | +| `getFragment()` | Retrieve the fragment component of the URI. | | +| `withScheme($scheme)` | Return an instance with the specified scheme. | | +| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | | +| `withHost($host)` | Return an instance with the specified host. | | +| `withPort($port)` | Return an instance with the specified port. | | +| `withPath($path)` | Return an instance with the specified path. | | +| `withQuery($query)` | Return an instance with the specified query string. | | +| `withFragment($fragment)` | Return an instance with the specified URI fragment. | | +| `__toString()` | Return the string representation as a URI reference. | | + +## `Psr\Http\Message\UploadedFileInterface` Methods + +| Method Name | Description | Notes | +|------------------------------------| ----------- | ----- | +| `getStream()` | Retrieve a stream representing the uploaded file. | | +| `moveTo($targetPath)` | Move the uploaded file to a new location. | | +| `getSize()` | Retrieve the file size. | | +| `getError()` | Retrieve the error associated with the uploaded file. | | +| `getClientFilename()` | Retrieve the filename sent by the client. | | +| `getClientMediaType()` | Retrieve the media type sent by the client. | | + +> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. +> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. + diff --git a/data/web/inc/lib/vendor/psr/http-message/docs/PSR7-Usage.md b/data/web/inc/lib/vendor/psr/http-message/docs/PSR7-Usage.md new file mode 100644 index 000000000..b6d048a34 --- /dev/null +++ b/data/web/inc/lib/vendor/psr/http-message/docs/PSR7-Usage.md @@ -0,0 +1,159 @@ +### PSR-7 Usage + +All PSR-7 applications comply with these interfaces +They were created to establish a standard between middleware implementations. + +> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. +> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. + + +The following examples will illustrate how basic operations are done in PSR-7. + +##### Examples + + +For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc) +All PSR-7 implementations should have the same behaviour. + +The following will be assumed: +`$request` is an object of `Psr\Http\Message\RequestInterface` and + +`$response` is an object implementing `Psr\Http\Message\RequestInterface` + + +### Working with HTTP Headers + +#### Adding headers to response: + +```php +$response->withHeader('My-Custom-Header', 'My Custom Message'); +``` + +#### Appending values to headers + +```php +$response->withAddedHeader('My-Custom-Header', 'The second message'); +``` + +#### Checking if header exists: + +```php +$request->hasHeader('My-Custom-Header'); // will return false +$response->hasHeader('My-Custom-Header'); // will return true +``` + +> Note: My-Custom-Header was only added in the Response + +#### Getting comma-separated values from a header (also applies to request) + +```php +// getting value from request headers +$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8" +// getting value from response headers +$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message" +``` + +#### Getting array of value from a header (also applies to request) +```php +// getting value from request headers +$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"] +// getting value from response headers +$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"] +``` + +#### Removing headers from HTTP Messages +```php +// removing a header from Request, removing deprecated "Content-MD5" header +$request->withoutHeader('Content-MD5'); + +// removing a header from Response +// effect: the browser won't know the size of the stream +// the browser will download the stream till it ends +$response->withoutHeader('Content-Length'); +``` + +### Working with HTTP Message Body + +When working with the PSR-7 there are two methods of implementation: +#### 1. Getting the body separately + +> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented. + +```php +$body = $response->getBody(); +// operations on body, eg. read, write, seek +// ... +// replacing the old body +$response->withBody($body); +// this last statement is optional as we working with objects +// in this case the "new" body is same with the "old" one +// the $body variable has the same value as the one in $request, only the reference is passed +``` + +#### 2. Working directly on response + +> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required + +```php +$response->getBody()->write('hello'); +``` + +### Getting the body contents + +The following snippet gets the contents of a stream contents. +> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream. +```php +$body = $response->getBody(); +$body->rewind(); // or $body->seek(0); +$bodyText = $body->getContents(); +``` +> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended. + +### Append to body + +```php +$response->getBody()->write('Hello'); // writing directly +$body = $request->getBody(); // which is a `StreamInterface` +$body->write('xxxxx'); +``` + +### Prepend to body +Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended. +The following example will explain the behaviour of streams. + +```php +// assuming our response is initially empty +$body = $repsonse->getBody(); +// writing the string "abcd" +$body->write('abcd'); + +// seeking to start of stream +$body->seek(0); +// writing 'ef' +$body->write('ef'); // at this point the stream contains "efcd" +``` + +#### Prepending by rewriting separately + +```php +// assuming our response body stream only contains: "abcd" +$body = $response->getBody(); +$body->rewind(); +$contents = $body->getContents(); // abcd +// seeking the stream to beginning +$body->rewind(); +$body->write('ef'); // stream contains "efcd" +$body->write($contents); // stream contains "efabcd" +``` + +> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`. + +#### Prepending by using contents as a string +```php +$body = $response->getBody(); +$body->rewind(); +$contents = $body->getContents(); // efabcd +$contents = 'ef'.$contents; +$body->rewind(); +$body->write($contents); +``` diff --git a/data/web/inc/lib/vendor/psr/http-message/src/MessageInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/MessageInterface.php index dd46e5ec8..a83c98518 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/MessageInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/MessageInterface.php @@ -23,7 +23,7 @@ interface MessageInterface * * @return string HTTP protocol version. */ - public function getProtocolVersion(); + public function getProtocolVersion(): string; /** * Return an instance with the specified HTTP protocol version. @@ -38,7 +38,7 @@ interface MessageInterface * @param string $version HTTP protocol version * @return static */ - public function withProtocolVersion($version); + public function withProtocolVersion(string $version): MessageInterface; /** * Retrieves all message header values. @@ -65,7 +65,7 @@ interface MessageInterface * key MUST be a header name, and each value MUST be an array of strings * for that header. */ - public function getHeaders(); + public function getHeaders(): array; /** * Checks if a header exists by the given case-insensitive name. @@ -75,7 +75,7 @@ interface MessageInterface * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ - public function hasHeader($name); + public function hasHeader(string $name): bool; /** * Retrieves a message header value by the given case-insensitive name. @@ -91,7 +91,7 @@ interface MessageInterface * header. If the header does not appear in the message, this method MUST * return an empty array. */ - public function getHeader($name); + public function getHeader(string $name): array; /** * Retrieves a comma-separated string of the values for a single header. @@ -112,7 +112,7 @@ interface MessageInterface * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ - public function getHeaderLine($name); + public function getHeaderLine(string $name): string; /** * Return an instance with the provided value replacing the specified header. @@ -129,7 +129,7 @@ interface MessageInterface * @return static * @throws \InvalidArgumentException for invalid header names or values. */ - public function withHeader($name, $value); + public function withHeader(string $name, $value): MessageInterface; /** * Return an instance with the specified header appended with the given value. @@ -147,7 +147,7 @@ interface MessageInterface * @return static * @throws \InvalidArgumentException for invalid header names or values. */ - public function withAddedHeader($name, $value); + public function withAddedHeader(string $name, $value): MessageInterface; /** * Return an instance without the specified header. @@ -161,14 +161,14 @@ interface MessageInterface * @param string $name Case-insensitive header field name to remove. * @return static */ - public function withoutHeader($name); + public function withoutHeader(string $name): MessageInterface; /** * Gets the body of the message. * * @return StreamInterface Returns the body as a stream. */ - public function getBody(); + public function getBody(): StreamInterface; /** * Return an instance with the specified message body. @@ -183,5 +183,5 @@ interface MessageInterface * @return static * @throws \InvalidArgumentException When the body is not valid. */ - public function withBody(StreamInterface $body); + public function withBody(StreamInterface $body): MessageInterface; } diff --git a/data/web/inc/lib/vendor/psr/http-message/src/RequestInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/RequestInterface.php index a96d4fd63..33f85e559 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/RequestInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/RequestInterface.php @@ -39,7 +39,7 @@ interface RequestInterface extends MessageInterface * * @return string */ - public function getRequestTarget(); + public function getRequestTarget(): string; /** * Return an instance with the specific request-target. @@ -55,17 +55,18 @@ interface RequestInterface extends MessageInterface * * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various * request-target forms allowed in request messages) - * @param mixed $requestTarget + * @param string $requestTarget * @return static */ - public function withRequestTarget($requestTarget); + public function withRequestTarget(string $requestTarget): RequestInterface; + /** * Retrieves the HTTP method of the request. * * @return string Returns the request method. */ - public function getMethod(); + public function getMethod(): string; /** * Return an instance with the provided HTTP method. @@ -82,7 +83,7 @@ interface RequestInterface extends MessageInterface * @return static * @throws \InvalidArgumentException for invalid HTTP methods. */ - public function withMethod($method); + public function withMethod(string $method): RequestInterface; /** * Retrieves the URI instance. @@ -93,7 +94,7 @@ interface RequestInterface extends MessageInterface * @return UriInterface Returns a UriInterface instance * representing the URI of the request. */ - public function getUri(); + public function getUri(): UriInterface; /** * Returns an instance with the provided URI. @@ -125,5 +126,5 @@ interface RequestInterface extends MessageInterface * @param bool $preserveHost Preserve the original state of the Host header. * @return static */ - public function withUri(UriInterface $uri, $preserveHost = false); + public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface; } diff --git a/data/web/inc/lib/vendor/psr/http-message/src/ResponseInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/ResponseInterface.php index c306514e6..e9299a914 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/ResponseInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/ResponseInterface.php @@ -27,7 +27,7 @@ interface ResponseInterface extends MessageInterface * * @return int Status code. */ - public function getStatusCode(); + public function getStatusCode(): int; /** * Return an instance with the specified status code and, optionally, reason phrase. @@ -49,7 +49,7 @@ interface ResponseInterface extends MessageInterface * @return static * @throws \InvalidArgumentException For invalid status code arguments. */ - public function withStatus($code, $reasonPhrase = ''); + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface; /** * Gets the response reason phrase associated with the status code. @@ -64,5 +64,5 @@ interface ResponseInterface extends MessageInterface * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml * @return string Reason phrase; must return an empty string if none present. */ - public function getReasonPhrase(); + public function getReasonPhrase(): string; } diff --git a/data/web/inc/lib/vendor/psr/http-message/src/ServerRequestInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/ServerRequestInterface.php index 02512340a..8625d0e10 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/ServerRequestInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/ServerRequestInterface.php @@ -51,7 +51,7 @@ interface ServerRequestInterface extends RequestInterface * * @return array */ - public function getServerParams(); + public function getServerParams(): array; /** * Retrieve cookies. @@ -63,7 +63,7 @@ interface ServerRequestInterface extends RequestInterface * * @return array */ - public function getCookieParams(); + public function getCookieParams(): array; /** * Return an instance with the specified cookies. @@ -82,7 +82,7 @@ interface ServerRequestInterface extends RequestInterface * @param array $cookies Array of key/value pairs representing cookies. * @return static */ - public function withCookieParams(array $cookies); + public function withCookieParams(array $cookies): ServerRequestInterface; /** * Retrieve query string arguments. @@ -96,7 +96,7 @@ interface ServerRequestInterface extends RequestInterface * * @return array */ - public function getQueryParams(); + public function getQueryParams(): array; /** * Return an instance with the specified query string arguments. @@ -120,7 +120,7 @@ interface ServerRequestInterface extends RequestInterface * $_GET. * @return static */ - public function withQueryParams(array $query); + public function withQueryParams(array $query): ServerRequestInterface; /** * Retrieve normalized file upload data. @@ -134,7 +134,7 @@ interface ServerRequestInterface extends RequestInterface * @return array An array tree of UploadedFileInterface instances; an empty * array MUST be returned if no data is present. */ - public function getUploadedFiles(); + public function getUploadedFiles(): array; /** * Create a new instance with the specified uploaded files. @@ -147,7 +147,7 @@ interface ServerRequestInterface extends RequestInterface * @return static * @throws \InvalidArgumentException if an invalid structure is provided. */ - public function withUploadedFiles(array $uploadedFiles); + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface; /** * Retrieve any parameters provided in the request body. @@ -194,7 +194,7 @@ interface ServerRequestInterface extends RequestInterface * @throws \InvalidArgumentException if an unsupported argument type is * provided. */ - public function withParsedBody($data); + public function withParsedBody($data): ServerRequestInterface; /** * Retrieve attributes derived from the request. @@ -207,7 +207,7 @@ interface ServerRequestInterface extends RequestInterface * * @return array Attributes derived from the request. */ - public function getAttributes(); + public function getAttributes(): array; /** * Retrieve a single derived request attribute. @@ -224,7 +224,7 @@ interface ServerRequestInterface extends RequestInterface * @param mixed $default Default value to return if the attribute does not exist. * @return mixed */ - public function getAttribute($name, $default = null); + public function getAttribute(string $name, $default = null); /** * Return an instance with the specified derived request attribute. @@ -241,7 +241,7 @@ interface ServerRequestInterface extends RequestInterface * @param mixed $value The value of the attribute. * @return static */ - public function withAttribute($name, $value); + public function withAttribute(string $name, $value): ServerRequestInterface; /** * Return an instance that removes the specified derived request attribute. @@ -257,5 +257,5 @@ interface ServerRequestInterface extends RequestInterface * @param string $name The attribute name. * @return static */ - public function withoutAttribute($name); + public function withoutAttribute(string $name): ServerRequestInterface; } diff --git a/data/web/inc/lib/vendor/psr/http-message/src/StreamInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/StreamInterface.php index f68f39126..a62aabb82 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/StreamInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/StreamInterface.php @@ -25,14 +25,14 @@ interface StreamInterface * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring * @return string */ - public function __toString(); + public function __toString(): string; /** * Closes the stream and any underlying resources. * * @return void */ - public function close(); + public function close(): void; /** * Separates any underlying resources from the stream. @@ -48,7 +48,7 @@ interface StreamInterface * * @return int|null Returns the size in bytes if known, or null if unknown. */ - public function getSize(); + public function getSize(): ?int; /** * Returns the current position of the file read/write pointer @@ -56,21 +56,21 @@ interface StreamInterface * @return int Position of the file pointer * @throws \RuntimeException on error. */ - public function tell(); + public function tell(): int; /** * Returns true if the stream is at the end of the stream. * * @return bool */ - public function eof(); + public function eof(): bool; /** * Returns whether or not the stream is seekable. * * @return bool */ - public function isSeekable(); + public function isSeekable(): bool; /** * Seek to a position in the stream. @@ -84,7 +84,7 @@ interface StreamInterface * SEEK_END: Set position to end-of-stream plus offset. * @throws \RuntimeException on failure. */ - public function seek($offset, $whence = SEEK_SET); + public function seek(int $offset, int $whence = SEEK_SET): void; /** * Seek to the beginning of the stream. @@ -96,14 +96,14 @@ interface StreamInterface * @link http://www.php.net/manual/en/function.fseek.php * @throws \RuntimeException on failure. */ - public function rewind(); + public function rewind(): void; /** * Returns whether or not the stream is writable. * * @return bool */ - public function isWritable(); + public function isWritable(): bool; /** * Write data to the stream. @@ -112,14 +112,14 @@ interface StreamInterface * @return int Returns the number of bytes written to the stream. * @throws \RuntimeException on failure. */ - public function write($string); + public function write(string $string): int; /** * Returns whether or not the stream is readable. * * @return bool */ - public function isReadable(); + public function isReadable(): bool; /** * Read data from the stream. @@ -131,7 +131,7 @@ interface StreamInterface * if no bytes are available. * @throws \RuntimeException if an error occurs. */ - public function read($length); + public function read(int $length): string; /** * Returns the remaining contents in a string @@ -140,7 +140,7 @@ interface StreamInterface * @throws \RuntimeException if unable to read or an error occurs while * reading. */ - public function getContents(); + public function getContents(): string; /** * Get stream metadata as an associative array or retrieve a specific key. @@ -149,10 +149,10 @@ interface StreamInterface * stream_get_meta_data() function. * * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. + * @param string|null $key Specific metadata to retrieve. * @return array|mixed|null Returns an associative array if no key is * provided. Returns a specific key value if a key is provided and the * value is found, or null if the key is not found. */ - public function getMetadata($key = null); + public function getMetadata(?string $key = null); } diff --git a/data/web/inc/lib/vendor/psr/http-message/src/UploadedFileInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/UploadedFileInterface.php index f8a6901e0..dd19d6538 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/UploadedFileInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/UploadedFileInterface.php @@ -28,7 +28,7 @@ interface UploadedFileInterface * @throws \RuntimeException in cases when no stream is available or can be * created. */ - public function getStream(); + public function getStream(): StreamInterface; /** * Move the uploaded file to a new location. @@ -62,7 +62,7 @@ interface UploadedFileInterface * @throws \RuntimeException on any error during the move operation, or on * the second or subsequent call to the method. */ - public function moveTo($targetPath); + public function moveTo(string $targetPath): void; /** * Retrieve the file size. @@ -73,7 +73,7 @@ interface UploadedFileInterface * * @return int|null The file size in bytes or null if unknown. */ - public function getSize(); + public function getSize(): ?int; /** * Retrieve the error associated with the uploaded file. @@ -89,7 +89,7 @@ interface UploadedFileInterface * @see http://php.net/manual/en/features.file-upload.errors.php * @return int One of PHP's UPLOAD_ERR_XXX constants. */ - public function getError(); + public function getError(): int; /** * Retrieve the filename sent by the client. @@ -104,7 +104,7 @@ interface UploadedFileInterface * @return string|null The filename sent by the client or null if none * was provided. */ - public function getClientFilename(); + public function getClientFilename(): ?string; /** * Retrieve the media type sent by the client. @@ -119,5 +119,5 @@ interface UploadedFileInterface * @return string|null The media type sent by the client or null if none * was provided. */ - public function getClientMediaType(); + public function getClientMediaType(): ?string; } diff --git a/data/web/inc/lib/vendor/psr/http-message/src/UriInterface.php b/data/web/inc/lib/vendor/psr/http-message/src/UriInterface.php index 9d7ab9eae..15e2cf286 100644 --- a/data/web/inc/lib/vendor/psr/http-message/src/UriInterface.php +++ b/data/web/inc/lib/vendor/psr/http-message/src/UriInterface.php @@ -1,4 +1,5 @@ value pairs in the cache, with an optional TTL. @@ -80,7 +80,7 @@ interface CacheInterface * MUST be thrown if $values is neither an array nor a Traversable, * or if any of the $values are not a legal value. */ - public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null); + public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool; /** * Deletes multiple cache items in a single operation. @@ -93,7 +93,7 @@ interface CacheInterface * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ - public function deleteMultiple(iterable $keys); + public function deleteMultiple(iterable $keys): bool; /** * Determines whether an item is present in the cache. @@ -110,5 +110,5 @@ interface CacheInterface * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ - public function has(string $key); + public function has(string $key): bool; } diff --git a/data/web/inc/lib/vendor/ralouphie/getallheaders/README.md b/data/web/inc/lib/vendor/ralouphie/getallheaders/README.md deleted file mode 100644 index 9430d76bb..000000000 --- a/data/web/inc/lib/vendor/ralouphie/getallheaders/README.md +++ /dev/null @@ -1,27 +0,0 @@ -getallheaders -============= - -PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3. - -[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders) -[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master) -[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders) -[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders) -[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders) - - -This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php). - -## Install - -For PHP version **`>= 5.6`**: - -``` -composer require ralouphie/getallheaders -``` - -For PHP version **`< 5.6`**: - -``` -composer require ralouphie/getallheaders "^2" -``` diff --git a/data/web/inc/lib/vendor/ralouphie/getallheaders/composer.json b/data/web/inc/lib/vendor/ralouphie/getallheaders/composer.json deleted file mode 100644 index de8ce62e4..000000000 --- a/data/web/inc/lib/vendor/ralouphie/getallheaders/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "ralouphie/getallheaders", - "description": "A polyfill for getallheaders.", - "license": "MIT", - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^5 || ^6.5", - "php-coveralls/php-coveralls": "^2.1" - }, - "autoload": { - "files": ["src/getallheaders.php"] - }, - "autoload-dev": { - "psr-4": { - "getallheaders\\Tests\\": "tests/" - } - } -} diff --git a/data/web/inc/lib/vendor/ralouphie/getallheaders/src/getallheaders.php b/data/web/inc/lib/vendor/ralouphie/getallheaders/src/getallheaders.php deleted file mode 100644 index c7285a5ba..000000000 --- a/data/web/inc/lib/vendor/ralouphie/getallheaders/src/getallheaders.php +++ /dev/null @@ -1,46 +0,0 @@ - 'Content-Type', - 'CONTENT_LENGTH' => 'Content-Length', - 'CONTENT_MD5' => 'Content-Md5', - ); - - foreach ($_SERVER as $key => $value) { - if (substr($key, 0, 5) === 'HTTP_') { - $key = substr($key, 5); - if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { - $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); - $headers[$key] = $value; - } - } elseif (isset($copy_server[$key])) { - $headers[$copy_server[$key]] = $value; - } - } - - if (!isset($headers['Authorization'])) { - if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { - $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; - } elseif (isset($_SERVER['PHP_AUTH_USER'])) { - $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; - $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); - } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { - $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; - } - } - - return $headers; - } - -} diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test-bacon.yml b/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test-bacon.yml new file mode 100644 index 000000000..98c45dd0f --- /dev/null +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test-bacon.yml @@ -0,0 +1,30 @@ +name: Test Bacon QR Code Provider + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] + + steps: + - uses: actions/checkout@v2 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer + coverage: xdebug + ini-values: error_reporting=E_ALL + + - uses: ramsey/composer-install@v1 + + - run: composer require bacon/bacon-qr-code + + - run: composer lint + - run: composer test testsDependency/BaconQRCodeTest.php diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test-endroid.yml b/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test-endroid.yml new file mode 100644 index 000000000..48b6e7413 --- /dev/null +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test-endroid.yml @@ -0,0 +1,46 @@ +name: Test Endroid QR Code Provider + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: ['8.0', '8.1'] + endroid-version: ["^4"] + include: + - php-version: 5.6 + # earliest supported version + endroid-version: 2.2.1 + - php-version: 7.0 + endroid-version: 2.5.1 + - php-version: 7.1 + # this version is 7.1+ + endroid-version: 3.0.0 + - php-version: 7.2 + # all later versions are 7.3+ + endroid-version: 3.5.9 + - php-version: 7.3 + endroid-version: 3.9.7 + - php-version: 7.4 + endroid-version: 4.0.0 + + steps: + - uses: actions/checkout@v2 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer + coverage: xdebug + ini-values: error_reporting=E_ALL + + - uses: ramsey/composer-install@v1 + + - run: composer require endroid/qrcode:${{ matrix.endroid-version }} + + - run: composer test testsDependency/EndroidQRCodeTest.php diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test.yml b/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test.yml index ba08ef230..c5f4c482e 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test.yml +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/.github/workflows/test.yml @@ -10,16 +10,17 @@ jobs: strategy: matrix: - php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} tools: composer coverage: xdebug + ini-values: error_reporting=E_ALL - uses: ramsey/composer-install@v1 diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/LICENSE b/data/web/inc/lib/vendor/robthree/twofactorauth/LICENSE index 75a96312c..b35300a7d 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/LICENSE +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2015 Rob Janssen +Copyright (c) 2014-2021 Rob Janssen and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json index b32e3bc45..3109f9ea5 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/composer.json @@ -1,7 +1,7 @@ { "name": "robthree/twofactorauth", "description": "Two Factor Authentication", - "version": "1.8.1", + "version": "1.8.2", "type": "library", "keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ], "homepage": "https://github.com/RobThree/TwoFactorAuth", diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php index 4d5cf0150..65fb97754 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/BaconQrCodeProvider.php @@ -125,12 +125,19 @@ class BaconQrCodeProvider implements IQRCodeProvider { if (is_string($colour) && $colour[0] == '#') { $hexToRGB = function ($input) { + // ensure input no longer has a # for more predictable division + // PHP 8.1 does not like implicitly casting a float to an int + $input = trim($input, '#'); + + if (strlen($input) != 3 && strlen($input) != 6) { + throw new \RuntimeException('Colour should be a 3 or 6 character value after the #'); + } + // split the array into three chunks - $split = str_split(trim($input, '#'), strlen($input) / 3); + $split = str_split($input, strlen($input) / 3); // cope with three character hex reference - // three characters plus a # = 4 - if (strlen($input) == 4) { + if (strlen($input) == 3) { array_walk($split, function (&$character) { $character = str_repeat($character, 2); }); diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php index 810aa9b5b..23fad9c64 100755 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeProvider.php @@ -1,8 +1,14 @@ endroid4 = method_exists(QrCode::class, 'create'); + $this->bgcolor = $this->handleColor($bgcolor); $this->color = $this->handleColor($color); $this->margin = $margin; @@ -26,7 +36,12 @@ class EndroidQrCodeProvider implements IQRCodeProvider public function getQRCodeImage($qrtext, $size) { - return $this->qrCodeInstance($qrtext, $size)->writeString(); + if (!$this->endroid4) { + return $this->qrCodeInstance($qrtext, $size)->writeString(); + } + + $writer = new PngWriter(); + return $writer->write($this->qrCodeInstance($qrtext, $size))->getString(); } protected function qrCodeInstance($qrtext, $size) @@ -49,22 +64,21 @@ class EndroidQrCodeProvider implements IQRCodeProvider $g = hexdec($split[1]); $b = hexdec($split[2]); - return ['r' => $r, 'g' => $g, 'b' => $b, 'a' => 0]; + return $this->endroid4 ? new Color($r, $g, $b, 0) : ['r' => $r, 'g' => $g, 'b' => $b, 'a' => 0]; } private function handleErrorCorrectionLevel($level) { switch ($level) { case 'L': - return ErrorCorrectionLevel::LOW(); + return $this->endroid4 ? new ErrorCorrectionLevelLow() : ErrorCorrectionLevel::LOW(); case 'M': - return ErrorCorrectionLevel::MEDIUM(); + return $this->endroid4 ? new ErrorCorrectionLevelMedium() : ErrorCorrectionLevel::MEDIUM(); case 'Q': - return ErrorCorrectionLevel::QUARTILE(); + return $this->endroid4 ? new ErrorCorrectionLevelQuartile() : ErrorCorrectionLevel::QUARTILE(); case 'H': - return ErrorCorrectionLevel::HIGH(); default: - return ErrorCorrectionLevel::HIGH(); + return $this->endroid4 ? new ErrorCorrectionLevelHigh() : ErrorCorrectionLevel::HIGH(); } } } diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php index ed8cc98c0..589a88d4e 100755 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/EndroidQrCodeWithLogoProvider.php @@ -1,8 +1,8 @@ logoSize = (array)$size; } + public function getQRCodeImage($qrtext, $size) + { + if (!$this->endroid4) { + return $this->qrCodeInstance($qrtext, $size)->writeString(); + } + + $logo = null; + if ($this->logoPath) { + $logo = Logo::create($this->logoPath); + if ($this->logoSize) { + $logo->setResizeToWidth($this->logoSize[0]); + if (isset($this->logoSize[1])) { + $logo->setResizeToHeight($this->logoSize[1]); + } + } + } + $writer = new PngWriter(); + return $writer->write($this->qrCodeInstance($qrtext, $size), $logo)->getString(); + } + protected function qrCodeInstance($qrtext, $size) { $qrCode = parent::qrCodeInstance($qrtext, $size); - if ($this->logoPath) { + if (!$this->endroid4 && $this->logoPath) { $qrCode->setLogoPath($this->logoPath); if ($this->logoSize) { - $qrCode->setLogoSize($this->logoSize[0], $this->logoSize[1]); + $qrCode->setLogoSize($this->logoSize[0], isset($this->logoSize[1]) ? $this->logoSize[1] : null); } } diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/HandlesDataUri.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/HandlesDataUri.php new file mode 100644 index 000000000..c9f8571eb --- /dev/null +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/Providers/Qr/HandlesDataUri.php @@ -0,0 +1,24 @@ +[\w\.\-\+\/]+);(?P\w+),(?P.*)/', $datauri, $m) === 1) { + return array( + 'mimetype' => $m['mimetype'], + 'encoding' => $m['encoding'], + 'data' => base64_decode($m['data']) + ); + } + + return null; + } +} diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php index c46ac1794..c94f80609 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/lib/TwoFactorAuth.php @@ -272,7 +272,7 @@ class TwoFactorAuth { return 'otpauth://totp/' . rawurlencode($label) . '?secret=' . rawurlencode($secret) - . '&issuer=' . rawurlencode($this->issuer) + . '&issuer=' . rawurlencode((string)$this->issuer) . '&period=' . intval($this->period) . '&algorithm=' . rawurlencode(strtoupper($this->algorithm)) . '&digits=' . intval($this->digits); diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml b/data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml index b2ff0040b..d7f9c74a1 100644 --- a/data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml @@ -2,6 +2,7 @@ [\w\.\-\/]+);(?P\w+),(?P.*)/', $datauri, $m) === 1) { - return array( - 'mimetype' => $m['mimetype'], - 'encoding' => $m['encoding'], - 'data' => base64_decode($m['data']) - ); - } - - return null; - } + use HandlesDataUri; /** * @return void @@ -40,6 +25,25 @@ class IQRCodeProviderTest extends TestCase $this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']); } + /** + * @return void + */ + public function testTotpUriIsCorrectNoIssuer() + { + $qr = new TestQrProvider(); + + /** + * The library specifies the issuer is null by default however in PHP 8.1 + * there is a deprecation warning for passing null as a string argument to rawurlencode + */ + + $tfa = new TwoFactorAuth(null, 6, 30, 'sha1', $qr); + $data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE')); + $this->assertEquals('test/test', $data['mimetype']); + $this->assertEquals('base64', $data['encoding']); + $this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']); + } + /** * @return void */ diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/testsDependency/BaconQRCodeTest.php b/data/web/inc/lib/vendor/robthree/twofactorauth/testsDependency/BaconQRCodeTest.php new file mode 100644 index 000000000..e46f13d8e --- /dev/null +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/testsDependency/BaconQRCodeTest.php @@ -0,0 +1,61 @@ +expectException(\RuntimeException::class); + + $qr = new BaconQrCodeProvider(1, '#000', '#FFF', 'svg'); + } else { + $qr = new BaconQrCodeProvider(1, '#000', '#FFF', 'svg'); + + $tfa = new TwoFactorAuth('Test&Issuer', 6, 30, 'sha1', $qr); + + $data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE')); + $this->assertEquals('image/svg+xml', $data['mimetype']); + } + } + + public function testBadTextColour() + { + $this->expectException(\RuntimeException::class); + + new BaconQrCodeProvider(1, 'not-a-colour', '#FFF'); + } + + public function testBadBackgroundColour() + { + $this->expectException(\RuntimeException::class); + + new BaconQrCodeProvider(1, '#000', 'not-a-colour'); + } + + public function testBadTextColourHexRef() + { + $this->expectException(\RuntimeException::class); + + new BaconQrCodeProvider(1, '#AAAA', '#FFF'); + } + + public function testBadBackgroundColourHexRef() + { + $this->expectException(\RuntimeException::class); + + new BaconQrCodeProvider(1, '#000', '#AAAA'); + } + + +} diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/testsDependency/EndroidQRCodeTest.php b/data/web/inc/lib/vendor/robthree/twofactorauth/testsDependency/EndroidQRCodeTest.php new file mode 100644 index 000000000..254138efb --- /dev/null +++ b/data/web/inc/lib/vendor/robthree/twofactorauth/testsDependency/EndroidQRCodeTest.php @@ -0,0 +1,24 @@ +DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE')); + $this->assertEquals('image/png', $data['mimetype']); + $this->assertEquals('base64', $data['encoding']); + $this->assertNotEmpty($data['data']); + + } +} diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/CONTRIBUTING.md b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/CONTRIBUTING.md index e1e078197..4cebd02ea 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/CONTRIBUTING.md +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/CONTRIBUTING.md @@ -7,8 +7,6 @@ We accept contributions via Pull Requests on [Github](https://github.com/stevenm ## Pull Requests -- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). - - **Add tests!** - Your patch won't be accepted if it doesn't have tests. - **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. @@ -29,14 +27,7 @@ We accept contributions via Pull Requests on [Github](https://github.com/stevenm ## Running Tests ``` bash -$ ./vendor/bin/phpunit -``` - - -## Running PHP Code Sniffer - -``` bash -$ ./vendor/bin/phpcs src --standard=psr2 -sp +$ composer test ``` **Happy coding**! diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md index d0b1acaf3..a948a79ec 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md @@ -1,7 +1,6 @@ # Keycloak Provider for OAuth 2.0 Client [![Latest Version](https://img.shields.io/github/release/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://github.com/stevenmaguire/oauth2-keycloak/releases) -[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -[![Build Status](https://img.shields.io/travis/stevenmaguire/oauth2-keycloak/master.svg?style=flat-square)](https://travis-ci.org/stevenmaguire/oauth2-keycloak) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://scrutinizer-ci.com/g/stevenmaguire/oauth2-keycloak/code-structure) [![Quality Score](https://img.shields.io/scrutinizer/g/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://scrutinizer-ci.com/g/stevenmaguire/oauth2-keycloak) [![Total Downloads](https://img.shields.io/packagist/dt/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://packagist.org/packages/stevenmaguire/oauth2-keycloak) @@ -20,9 +19,11 @@ composer require stevenmaguire/oauth2-keycloak Usage is the same as The League's OAuth client, using `\Stevenmaguire\OAuth2\Client\Provider\Keycloak` as the provider. -Use `authServerUrl` to specify the Keycloak server URL. You can lookup the correct value from the Keycloak client installer JSON under `auth-server-url`, eg. `http://localhost:8080/auth`. +Use `authServerUrl` to specify the Keycloak server URL. This depends on your Keycloak version: +- legacy releases often use `http://localhost:8080/auth` +- modern releases (Keycloak 17+) typically use `http://localhost:8080` -Use `realm` to specify the Keycloak realm name. You can lookup the correct value from the Keycloak client installer JSON under `resource`, eg. `master`. +Use `realm` to specify the Keycloak realm name. In client configuration exports this is represented as `realm`, e.g. `master`. ### Authorization Code Flow @@ -34,8 +35,8 @@ $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 'clientSecret' => '{keycloak-client-secret}', 'redirectUri' => 'https://example.com/callback-url', 'encryptionAlgorithm' => 'RS256', // optional - 'encryptionKeyPath' => '../key.pem' // optional - 'encryptionKey' => 'contents_of_key_or_certificate' // optional + 'encryptionKeyPath' => '../key.pem', // optional + 'encryptionKey' => 'contents_of_key_or_certificate', // optional 'version' => '20.0.1', // optional ]); @@ -55,7 +56,7 @@ if (!isset($_GET['code'])) { } else { - // Try to get an access token (using the authorization coe grant) + // Try to get an access token (using the authorization code grant) try { $token = $provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'] @@ -155,8 +156,8 @@ $provider->setEncryptionKeyPath($keyPath); ## Testing -``` bash -$ ./vendor/bin/phpunit +```bash +$ composer test ``` ## Contributing diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json index b6a54477e..61d0b7bd7 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json @@ -18,14 +18,15 @@ "keycloak" ], "require": { - "php": "~7.2 || ~8.0", - "league/oauth2-client": "^2.0", - "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0" + "php": "^8.0", + "league/oauth2-client": "^2.8", + "firebase/php-jwt": "^7.0" }, "require-dev": { "phpunit/phpunit": "~9.6.4", - "mockery/mockery": "~1.5.0", - "squizlabs/php_codesniffer": "~3.7.0" + "mockery/mockery": "^1.6", + "squizlabs/php_codesniffer": "~3.7.0", + "phpstan/phpstan": "^1.12" }, "autoload": { "psr-4": { @@ -37,6 +38,11 @@ "Stevenmaguire\\OAuth2\\Client\\Test\\": "test/src/" } }, + "config": { + "platform": { + "php": "8.0.0" + } + }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" @@ -48,4 +54,4 @@ "phpunit --colors=always" ] } -} \ No newline at end of file +} diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpstan.neon b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpstan.neon new file mode 100644 index 000000000..776ccd86c --- /dev/null +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: max + paths: + - src diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Exception/EncryptionConfigurationException.php b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Exception/EncryptionConfigurationException.php index 546d2de83..49b3b4822 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Exception/EncryptionConfigurationException.php +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Exception/EncryptionConfigurationException.php @@ -11,9 +11,9 @@ class EncryptionConfigurationException extends Exception * * @return \Stevenmaguire\OAuth2\Client\Provider\Exception\EncryptionConfigurationException */ - public static function undeterminedEncryption() + public static function undeterminedEncryption(): self { - return new static( + return new self( 'The given response may be encrypted and sufficient '. 'encryption configuration has not been provided.', 400 diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Keycloak.php b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Keycloak.php index bc41a7138..a8ce719d6 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Keycloak.php +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/Keycloak.php @@ -4,6 +4,7 @@ namespace Stevenmaguire\OAuth2\Client\Provider; use Exception; use Firebase\JWT\JWT; +use Firebase\JWT\Key; use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Token\AccessToken; @@ -37,43 +38,43 @@ class Keycloak extends AbstractProvider * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 * for a list of spec-compliant algorithms. * - * @var string + * @var string|null */ public $encryptionAlgorithm = null; /** * Encryption key. * - * @var string + * @var string|null */ public $encryptionKey = null; /** - * Keycloak version. - * - * @var string - */ + * Keycloak version. + * + * @var string|null + */ public $version = null; /** * Constructs an OAuth 2.0 service provider. * - * @param array $options An array of options to set on this provider. + * @param array $options An array of options to set on this provider. * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. * Individual providers may introduce more options, as needed. - * @param array $collaborators An array of collaborators that may be used to + * @param array $collaborators An array of collaborators that may be used to * override this provider's default behavior. Collaborators include * `grantFactory`, `requestFactory`, `httpClient`, and `randomFactory`. * Individual providers may introduce more collaborators, as needed. */ public function __construct(array $options = [], array $collaborators = []) { - if (isset($options['encryptionKeyPath'])) { + if (isset($options['encryptionKeyPath']) && is_string($options['encryptionKeyPath'])) { $this->setEncryptionKeyPath($options['encryptionKeyPath']); unset($options['encryptionKeyPath']); } - if (isset($options['version'])) { + if (isset($options['version']) && is_string($options['version'])) { $this->setVersion($options['version']); } @@ -83,27 +84,39 @@ class Keycloak extends AbstractProvider /** * Attempts to decrypt the given response. * - * @param string|array|null $response + * @param string|array|null $response * - * @return string|array|null + * @return string|array|null + * @throws EncryptionConfigurationException */ - public function decryptResponse($response) + public function decryptResponse($response): array|string|null { if (!is_string($response)) { return $response; } if ($this->usesEncryption()) { - return json_decode( - json_encode( - JWT::decode( - $response, - $this->encryptionKey, - array($this->encryptionAlgorithm) - ) - ), - true + if (!is_string($this->encryptionKey) || !is_string($this->encryptionAlgorithm)) { + throw EncryptionConfigurationException::undeterminedEncryption(); + } + + $tokenData = JWT::decode( + $response, + new Key( + $this->encryptionKey, + $this->encryptionAlgorithm + ) ); + $tokenJson = json_encode($tokenData); + if (!is_string($tokenJson)) { + throw EncryptionConfigurationException::undeterminedEncryption(); + } + $decodedToken = json_decode($tokenJson, true); + if (!is_array($decodedToken)) { + throw EncryptionConfigurationException::undeterminedEncryption(); + } + + return $decodedToken; } throw EncryptionConfigurationException::undeterminedEncryption(); @@ -114,7 +127,7 @@ class Keycloak extends AbstractProvider * * @return string */ - public function getBaseAuthorizationUrl() + public function getBaseAuthorizationUrl(): string { return $this->getBaseUrlWithRealm().'/protocol/openid-connect/auth'; } @@ -122,11 +135,11 @@ class Keycloak extends AbstractProvider /** * Get access token url to retrieve token * - * @param array $params + * @param array $params * * @return string */ - public function getBaseAccessTokenUrl(array $params) + public function getBaseAccessTokenUrl(array $params): string { return $this->getBaseUrlWithRealm().'/protocol/openid-connect/token'; } @@ -138,7 +151,7 @@ class Keycloak extends AbstractProvider * * @return string */ - public function getResourceOwnerDetailsUrl(AccessToken $token) + public function getResourceOwnerDetailsUrl(AccessToken $token): string { return $this->getBaseUrlWithRealm().'/protocol/openid-connect/userinfo'; } @@ -146,10 +159,10 @@ class Keycloak extends AbstractProvider /** * Builds the logout URL. * - * @param array $options + * @param array $options * @return string Authorization URL */ - public function getLogoutUrl(array $options = []) + public function getLogoutUrl(array $options = []): string { $base = $this->getBaseLogoutUrl(); $params = $this->getAuthorizationParameters($options); @@ -158,10 +171,13 @@ class Keycloak extends AbstractProvider // As of this version the parameter is called post_logout_redirect_uri. In addition to this // a parameter id_token_hint has to be provided. if ($this->validateGteVersion('18.0.0')) { - if (isset($options['access_token']) === true) { + if (isset($options['access_token']) === true && $options['access_token'] instanceof AccessToken) { $accessToken = $options['access_token']; + $values = $accessToken->getValues(); - $params['id_token_hint'] = $accessToken->getValues()['id_token']; + if (isset($values['id_token']) && is_string($values['id_token'])) { + $params['id_token_hint'] = $values['id_token']; + } $params['post_logout_redirect_uri'] = $params['redirect_uri']; } @@ -177,7 +193,7 @@ class Keycloak extends AbstractProvider * * @return string */ - private function getBaseLogoutUrl() + private function getBaseLogoutUrl(): string { return $this->getBaseUrlWithRealm() . '/protocol/openid-connect/logout'; } @@ -187,7 +203,7 @@ class Keycloak extends AbstractProvider * * @return string */ - protected function getBaseUrlWithRealm() + protected function getBaseUrlWithRealm(): string { return $this->authServerUrl.'/realms/'.$this->realm; } @@ -200,7 +216,7 @@ class Keycloak extends AbstractProvider * * @return string[] */ - protected function getDefaultScopes() + protected function getDefaultScopes(): array { $scopes = [ 'profile', @@ -218,7 +234,7 @@ class Keycloak extends AbstractProvider * * @return string Scope separator, defaults to ',' */ - protected function getScopeSeparator() + protected function getScopeSeparator(): string { return ' '; } @@ -229,28 +245,32 @@ class Keycloak extends AbstractProvider * * @throws IdentityProviderException * @param ResponseInterface $response - * @param string $data Parsed response data + * @param mixed $data Parsed response data * @return void */ - protected function checkResponse(ResponseInterface $response, $data) + protected function checkResponse(ResponseInterface $response, $data): void { + if (!is_array($data)) { + return; + } + if (!empty($data['error'])) { $error = $data['error']; if (isset($data['error_description'])) { - $error.=': '.$data['error_description']; + $error .= ': '.$data['error_description']; } - throw new IdentityProviderException($error, 0, $data); + throw new IdentityProviderException($error, $response->getStatusCode(), $data); } } /** * Generate a user object from a successful user details request. * - * @param array $response + * @param array $response * @param AccessToken $token * @return KeycloakResourceOwner */ - protected function createResourceOwner(array $response, AccessToken $token) + protected function createResourceOwner(array $response, AccessToken $token): KeycloakResourceOwner { return new KeycloakResourceOwner($response); } @@ -262,9 +282,12 @@ class Keycloak extends AbstractProvider * @return KeycloakResourceOwner * @throws EncryptionConfigurationException */ - public function getResourceOwner(AccessToken $token) + public function getResourceOwner(AccessToken $token): KeycloakResourceOwner { $response = $this->fetchResourceOwnerDetails($token); + if (!is_array($response)) { + throw new UnexpectedValueException('Expected resource owner details to be an array.'); + } // We are always getting an array. We have to check if it is // the array we created @@ -273,6 +296,9 @@ class Keycloak extends AbstractProvider } $response = $this->decryptResponse($response); + if (!is_array($response)) { + throw new UnexpectedValueException('Expected decrypted resource owner details to be an array.'); + } return $this->createResourceOwner($response, $token); } @@ -284,7 +310,7 @@ class Keycloak extends AbstractProvider * * @return Keycloak */ - public function setEncryptionAlgorithm($encryptionAlgorithm) + public function setEncryptionAlgorithm(string $encryptionAlgorithm): self { $this->encryptionAlgorithm = $encryptionAlgorithm; @@ -298,7 +324,7 @@ class Keycloak extends AbstractProvider * * @return Keycloak */ - public function setEncryptionKey($encryptionKey) + public function setEncryptionKey(string $encryptionKey): self { $this->encryptionKey = $encryptionKey; @@ -313,10 +339,13 @@ class Keycloak extends AbstractProvider * * @return Keycloak */ - public function setEncryptionKeyPath($encryptionKeyPath) + public function setEncryptionKeyPath(string $encryptionKeyPath): self { try { - $this->encryptionKey = file_get_contents($encryptionKeyPath); + $encryptionKey = file_get_contents($encryptionKeyPath); + if (is_string($encryptionKey)) { + $this->encryptionKey = $encryptionKey; + } } catch (Exception $e) { // Not sure how to handle this yet. } @@ -331,7 +360,7 @@ class Keycloak extends AbstractProvider * * @return Keycloak */ - public function setVersion($version) + public function setVersion(string $version): self { $this->version = $version; @@ -343,7 +372,7 @@ class Keycloak extends AbstractProvider * * @return bool */ - public function usesEncryption() + public function usesEncryption(): bool { return (bool) $this->encryptionAlgorithm && $this->encryptionKey; } @@ -353,16 +382,15 @@ class Keycloak extends AbstractProvider * * @throws UnexpectedValueException * @param ResponseInterface $response - * @return array + * @return string|array */ - protected function parseResponse(ResponseInterface $response) + protected function parseResponse(ResponseInterface $response): string|array { // We have a problem with keycloak when the userinfo responses // with a jwt token // Because it just return a jwt as string with the header // application/jwt // This can't be parsed to a array - // Dont know why this function only allow an array as return value... $content = (string) $response->getBody(); $type = $this->getContentType($response); @@ -380,8 +408,8 @@ class Keycloak extends AbstractProvider * @param string $version * @return bool */ - private function validateGteVersion($version) + private function validateGteVersion(string $version): bool { - return (isset($this->version) && version_compare($this->version, $version, '>=')); + return is_string($this->version) && version_compare($this->version, $version, '>='); } } diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/KeycloakResourceOwner.php b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/KeycloakResourceOwner.php index 7587af783..708dd5e4b 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/KeycloakResourceOwner.php +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/src/Provider/KeycloakResourceOwner.php @@ -9,14 +9,14 @@ class KeycloakResourceOwner implements ResourceOwnerInterface /** * Raw response * - * @var array + * @var array */ protected $response; /** * Creates new resource owner. * - * @param array $response + * @param array $response */ public function __construct(array $response = array()) { @@ -28,9 +28,16 @@ class KeycloakResourceOwner implements ResourceOwnerInterface * * @return string|null */ - public function getId() + public function getId(): ?string { - return \array_key_exists('sub', $this->response) ? $this->response['sub'] : null; + if (!\array_key_exists('sub', $this->response)) { + return null; + } + if (!is_scalar($this->response['sub'])) { + return null; + } + + return (string) $this->response['sub']; } /** @@ -38,9 +45,13 @@ class KeycloakResourceOwner implements ResourceOwnerInterface * * @return string|null */ - public function getEmail() + public function getEmail(): ?string { - return \array_key_exists('email', $this->response) ? $this->response['email'] : null; + if (!\array_key_exists('email', $this->response) || !is_string($this->response['email'])) { + return null; + } + + return $this->response['email']; } /** @@ -48,17 +59,63 @@ class KeycloakResourceOwner implements ResourceOwnerInterface * * @return string|null */ - public function getName() + public function getName(): ?string { - return \array_key_exists('name', $this->response) ? $this->response['name'] : null; + if (!\array_key_exists('name', $this->response) || !is_string($this->response['name'])) { + return null; + } + + return $this->response['name']; + } + + /** + * Get resource owner username + * + * @return string|null + */ + public function getUsername(): ?string + { + if (!\array_key_exists('preferred_username', $this->response) || !is_string($this->response['preferred_username'])) { + return null; + } + + return $this->response['preferred_username']; + } + + /** + * Get resource owner first name + * + * @return string|null + */ + public function getFirstName(): ?string + { + if (!\array_key_exists('given_name', $this->response) || !is_string($this->response['given_name'])) { + return null; + } + + return $this->response['given_name']; + } + + /** + * Get resource owner last name + * + * @return string|null + */ + public function getLastName(): ?string + { + if (!\array_key_exists('family_name', $this->response) || !is_string($this->response['family_name'])) { + return null; + } + + return $this->response['family_name']; } /** * Return all of the owner details available as an array. * - * @return array + * @return array */ - public function toArray() + public function toArray(): array { return $this->response; } diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php index a5c1da14f..8272f7af0 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php @@ -7,7 +7,7 @@ namespace namespace Stevenmaguire\OAuth2\Client\Provider { - function file_get_contents() + function file_get_contents(...$args): string|false { global $mockFileGetContents; if (isset($mockFileGetContents) && ! is_null($mockFileGetContents)) { @@ -16,17 +16,22 @@ namespace Stevenmaguire\OAuth2\Client\Provider } return $mockFileGetContents; } else { - return call_user_func_array('\file_get_contents', func_get_args()); + return \file_get_contents(...$args); } } } namespace Stevenmaguire\OAuth2\Client\Test\Provider { + use DateInterval; + use DateTimeImmutable; + use Firebase\JWT\JWT; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; + use League\OAuth2\Client\Token\AccessToken; use League\OAuth2\Client\Tool\QueryBuilderTrait; use Mockery as m; use PHPUnit\Framework\TestCase; + use Psr\Http\Message\StreamInterface; use Stevenmaguire\OAuth2\Client\Provider\Exception\EncryptionConfigurationException; use Stevenmaguire\OAuth2\Client\Provider\Keycloak; @@ -34,7 +39,59 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider { use QueryBuilderTrait; - protected $provider; + public const ENCRYPTION_KEY = <<provider->getAuthorizationUrl(); $uri = parse_url($url); @@ -68,7 +125,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertNotNull($this->provider->getState()); } - public function testEncryptionAlgorithm() + public function testEncryptionAlgorithm(): void { $algorithm = uniqid(); $provider = new Keycloak([ @@ -83,7 +140,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals($algorithm, $provider->encryptionAlgorithm); } - public function testEncryptionKey() + public function testEncryptionKey(): void { $key = uniqid(); $provider = new Keycloak([ @@ -98,7 +155,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals($key, $provider->encryptionKey); } - public function testEncryptionKeyPath() + public function testEncryptionKeyPath(): void { global $mockFileGetContents; $path = uniqid(); @@ -120,7 +177,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals($key, $provider->encryptionKey); } - public function testEncryptionKeyPathFails() + public function testEncryptionKeyPathFails(): void { $this->markTestIncomplete('Need to assess the test to see what is required to be checked.'); @@ -136,7 +193,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $provider->setEncryptionKeyPath($path); } - public function testScopes() + public function testScopes(): void { $scopeSeparator = ' '; $options = ['scope' => [uniqid(), uniqid()]]; @@ -146,7 +203,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertStringContainsString($encodedScope, $url); } - public function testGetAuthorizationUrl() + public function testGetAuthorizationUrl(): void { $url = $this->provider->getAuthorizationUrl(); $uri = parse_url($url); @@ -154,7 +211,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/auth', $uri['path']); } - public function testGetLogoutUrl() + public function testGetLogoutUrl(): void { $url = $this->provider->getLogoutUrl(); $uri = parse_url($url); @@ -162,7 +219,26 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/logout', $uri['path']); } - public function testGetBaseAccessTokenUrl() + public function testGetLogoutUrlWithIdTokenHint(): void + { + $this->provider->setVersion('18.0.0'); + + $options = [ + 'access_token' => new AccessToken( + [ + 'id_token' => 'the_id_token', + 'access_token' => 'the_access_token', + ] + ), + ]; + $url = $this->provider->getLogoutUrl($options); + $uri = parse_url($url); + + $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/logout', $uri['path']); + $this->assertStringContainsString('id_token_hint=the_id_token', $uri['query']); + } + + public function testGetBaseAccessTokenUrl(): void { $params = []; @@ -172,12 +248,19 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/token', $uri['path']); } - public function testGetAccessToken() + public function testGetAccessToken(): void { + $stream = $this->createMock(StreamInterface::class); + $stream + ->method('__toString') + ->willReturn('{"access_token":"mock_access_token","scope":"email","token_type":"bearer"}'); + $response = m::mock('Psr\Http\Message\ResponseInterface'); - $response->shouldReceive('getBody') - ->andReturn('{"access_token":"mock_access_token", "scope":"email", "token_type":"bearer"}'); - $response->shouldReceive('getHeader') + $response + ->shouldReceive('getBody') + ->andReturn($stream); + $response + ->shouldReceive('getHeader') ->andReturn(['content-type' => 'json']); $client = m::mock('GuzzleHttp\ClientInterface'); @@ -186,7 +269,9 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider ->andReturn($response); $this->provider->setHttpClient($client); - $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + $token = $this + ->provider + ->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); $this->assertEquals('mock_access_token', $token->getToken()); $this->assertNull($token->getExpires()); @@ -194,34 +279,66 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertNull($token->getResourceOwnerId()); } - public function testUserData() + public function testUserData(): void { $userId = rand(1000, 9999); $name = uniqid(); - $nickname = uniqid(); $email = uniqid(); + $username = uniqid(); + $firstName = uniqid(); + $lastName = uniqid(); + + $getAccessTokenResponseStream = $this->createMock(StreamInterface::class); + $getAccessTokenResponseStream + ->method('__toString') + ->willReturn( + '{"access_token":"mock_access_token","expires":"3600","refresh_token":"mock_refresh_token","otherKey":[1234]}' + ); $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $postResponse->shouldReceive('getBody') - ->andReturn( - 'access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}' + $postResponse + ->shouldReceive('getBody') + ->andReturn($getAccessTokenResponseStream); + $postResponse + ->shouldReceive('getHeader') + ->andReturn(['content-type' => 'json']); + + $getResourceOwnerResponseStream = $this->createMock(StreamInterface::class); + $getResourceOwnerResponseStream + ->method('__toString') + ->willReturn( + sprintf( + '{"sub": "%s", "name": "%s", "email": "%s", "preferred_username": "%s", "given_name": "%s", "family_name": "%s"}', + $userId, + $name, + $email, + $username, + $firstName, + $lastName + ) ); - $postResponse->shouldReceive('getHeader') - ->andReturn(['content-type' => 'application/x-www-form-urlencoded']); $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $userResponse->shouldReceive('getBody') - ->andReturn('{"sub": '.$userId.', "name": "'.$name.'", "email": "'.$email.'"}'); - $userResponse->shouldReceive('getHeader') + $userResponse + ->shouldReceive('getBody') + ->andReturn($getResourceOwnerResponseStream); + $userResponse + ->shouldReceive('getHeader') ->andReturn(['content-type' => 'json']); $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send') - ->times(2) + $client + ->shouldReceive('send') ->andReturn($postResponse, $userResponse); $this->provider->setHttpClient($client); - $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + $token = $this->provider->getAccessToken( + 'authorization_code', + [ + 'code' => 'mock_authorization_code', + 'access_token' => 'mock_access_token', + ] + ); $user = $this->provider->getResourceOwner($token); $this->assertEquals($userId, $user->getId()); @@ -230,113 +347,219 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals($name, $user->toArray()['name']); $this->assertEquals($email, $user->getEmail()); $this->assertEquals($email, $user->toArray()['email']); + $this->assertEquals($username, $user->getUsername()); + $this->assertEquals($username, $user->toArray()['preferred_username']); + $this->assertEquals($firstName, $user->getFirstName()); + $this->assertEquals($firstName, $user->toArray()['given_name']); + $this->assertEquals($lastName, $user->getLastName()); + $this->assertEquals($lastName, $user->toArray()['family_name']); } - public function testUserDataWithEncryption() + public function testUserDataWithEncryption(): void { - $userId = rand(1000, 9999); - $name = uniqid(); - $nickname = uniqid(); - $email = uniqid(); - $jwt = uniqid(); - $algorithm = uniqid(); - $key = uniqid(); + $jwt = JWT::encode( + json_decode( + sprintf( + $this->jwtTemplate, + (new DateTimeImmutable())->add(new DateInterval('PT1H'))->getTimestamp(), + (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp(), + (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp() + ), + true + ), + self::ENCRYPTION_KEY, + self::ENCRYPTION_ALGORITHM + ); - $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $postResponse->shouldReceive('getBody') - ->andReturn( - 'access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}' + $getAccessTokenResponseStream = $this->createMock(StreamInterface::class); + $getAccessTokenResponseStream + ->method('__toString') + ->willReturn( + sprintf( + '{"access_token":"%s","expires":"3600","refresh_token":"mock_refresh_token","otherKey":[1234]}', + $jwt + ) ); - $postResponse->shouldReceive('getHeader') - ->andReturn(['content-type' => 'application/x-www-form-urlencoded']); - $postResponse->shouldReceive('getStatusCode') + + $accessTokenResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $accessTokenResponse + ->shouldReceive('getBody') + ->andReturn($getAccessTokenResponseStream); + $accessTokenResponse + ->shouldReceive('getHeader') + ->andReturn(['content-type' => 'json']); + $accessTokenResponse + ->shouldReceive('getStatusCode') ->andReturn(200); - $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $userResponse->shouldReceive('getBody') - ->andReturn($jwt); - $userResponse->shouldReceive('getHeader') + $getResourceOwnerResponseStream = $this->createMock(StreamInterface::class); + $getResourceOwnerResponseStream + ->method('__toString') + ->willReturn($jwt); + + $resourceOwnerResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $resourceOwnerResponse + ->shouldReceive('getBody') + ->andReturn($getResourceOwnerResponseStream); + $resourceOwnerResponse + ->shouldReceive('getHeader') ->andReturn(['content-type' => 'application/jwt']); - $userResponse->shouldReceive('getStatusCode') + $resourceOwnerResponse + ->shouldReceive('getStatusCode') ->andReturn(200); - $decoder = \Mockery::mock('overload:Firebase\JWT\JWT'); - $decoder->shouldReceive('decode') - ->with($jwt, $key, [$algorithm]) - ->andReturn([ - 'sub' => $userId, - 'email' => $email, - 'name' => $name, - ]); - $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send') + $client + ->shouldReceive('send') ->times(2) - ->andReturn($postResponse, $userResponse); + ->andReturn($accessTokenResponse, $resourceOwnerResponse); $this->provider->setHttpClient($client); - $token = $this->provider->setEncryptionAlgorithm($algorithm) - ->setEncryptionKey($key) + $token = $this + ->provider + ->setEncryptionAlgorithm(self::ENCRYPTION_ALGORITHM) + ->setEncryptionKey(self::ENCRYPTION_KEY) ->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); $user = $this->provider->getResourceOwner($token); + $email = "test-user@example.org"; + $name = "Test User"; + $userId = "4332085e-b944-4acc-9eb1-27d8f5405f3e"; + $username = "test-user"; + $firstName = "Test"; + $lastName = "User"; + $this->assertEquals($userId, $user->getId()); $this->assertEquals($userId, $user->toArray()['sub']); $this->assertEquals($name, $user->getName()); $this->assertEquals($name, $user->toArray()['name']); $this->assertEquals($email, $user->getEmail()); $this->assertEquals($email, $user->toArray()['email']); + $this->assertEquals($username, $user->getUsername()); + $this->assertEquals($username, $user->toArray()['preferred_username']); + $this->assertEquals($firstName, $user->getFirstName()); + $this->assertEquals($firstName, $user->toArray()['given_name']); + $this->assertEquals($lastName, $user->getLastName()); + $this->assertEquals($lastName, $user->toArray()['family_name']); } - public function testUserDataFailsWhenEncryptionEncounteredAndNotConfigured() + public function testUserDataFailsWhenEncryptionEncounteredAndNotConfigured(): void { $this->expectException(EncryptionConfigurationException::class); - $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $postResponse->shouldReceive('getBody') - ->andReturn( - 'access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}' + $accessTokenResponseStream = $this->createMock(StreamInterface::class); + $accessTokenResponseStream + ->method('__toString') + ->willReturn( + '{"access_token":"mock_access_token","expires":"3600","refresh_token":"mock_refresh_token","otherKey":[1234]}' ); - $postResponse->shouldReceive('getHeader') - ->andReturn(['content-type' => 'application/x-www-form-urlencoded']); - $postResponse->shouldReceive('getStatusCode') + + $getAccessTokenResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $getAccessTokenResponse + ->shouldReceive('getBody') + ->andReturn($accessTokenResponseStream); + $getAccessTokenResponse + ->shouldReceive('getHeader') + ->andReturn(['content-type' => 'json']); + $getAccessTokenResponse + ->shouldReceive('getStatusCode') ->andReturn(200); - $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $userResponse->shouldReceive('getBody') - ->andReturn(uniqid()); - $userResponse->shouldReceive('getHeader') + $resourceOwnerResponseStream = $this->createMock(StreamInterface::class); + $resourceOwnerResponseStream + ->method('__toString') + ->willReturn(uniqid()); + + $getResourceOwnerResponse = m::mock('Psr\Http\Message\ResponseInterface'); + $getResourceOwnerResponse + ->shouldReceive('getBody') + ->andReturn($resourceOwnerResponseStream); + $getResourceOwnerResponse + ->shouldReceive('getHeader') ->andReturn(['content-type' => 'application/jwt']); - $userResponse->shouldReceive('getStatusCode') + $getResourceOwnerResponse + ->shouldReceive('getStatusCode') ->andReturn(200); $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send') + $client + ->shouldReceive('send') ->times(2) - ->andReturn($postResponse, $userResponse); + ->andReturn($getAccessTokenResponse, $getResourceOwnerResponse); $this->provider->setHttpClient($client); - $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); + $token = $this->provider->getAccessToken( + 'authorization_code', # + ['code' => 'mock_authorization_code'] + ); $user = $this->provider->getResourceOwner($token); } - public function testErrorResponse() + public function testErrorResponse(): void { $this->expectException(IdentityProviderException::class); + $accessTokenResponseStream = $this->createMock(StreamInterface::class); + $accessTokenResponseStream + ->method('__toString') + ->willReturn( + '{"error": "invalid_grant", "error_description": "Code not found"}' + ); + $response = m::mock('Psr\Http\Message\ResponseInterface'); - $response->shouldReceive('getBody') - ->andReturn('{"error": "invalid_grant", "error_description": "Code not found"}'); - $response->shouldReceive('getHeader') + $response + ->shouldReceive('getBody') + ->andReturn($accessTokenResponseStream); + $response + ->shouldReceive('getHeader') ->andReturn(['content-type' => 'json']); + $response + ->shouldReceive('getStatusCode') + ->andReturn(401); $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send') + $client + ->shouldReceive('send') ->times(1) ->andReturn($response); $this->provider->setHttpClient($client); $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); } + + public function testCanDecryptResponseThrowsExceptionIfResponseIsNotAStringAndEncryptionIsNotUsed(): void + { + $this->expectException(EncryptionConfigurationException::class); + + $this->provider->decryptResponse(''); + + $this->assertFalse($this->provider->usesEncryption()); + } + + public function testCanDecryptResponseReturnsResponseWhenEncryptionIsUsed(): void + { + $jwtPayload = json_decode( + sprintf( + $this->jwtTemplate, + (new DateTimeImmutable())->add(new DateInterval('PT1H'))->getTimestamp(), + (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp(), + (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp() + ), + true + ); + $jwt = JWT::encode( + $jwtPayload, + self::ENCRYPTION_KEY, + self::ENCRYPTION_ALGORITHM + ); + + $this->provider + ->setEncryptionAlgorithm(self::ENCRYPTION_ALGORITHM) + ->setEncryptionKey(self::ENCRYPTION_KEY); + + $response = $this->provider->decryptResponse($jwt); + + $this->assertSame($jwtPayload, $response); + } } } diff --git a/data/web/inc/lib/vendor/symfony/clock/CHANGELOG.md b/data/web/inc/lib/vendor/symfony/clock/CHANGELOG.md new file mode 100644 index 000000000..c71cb5c43 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/CHANGELOG.md @@ -0,0 +1,25 @@ +CHANGELOG +========= + +7.1 +--- + + * Add `DatePoint::getMicrosecond()` and `DatePoint::setMicrosecond()` + +6.4 +--- + + * Add `DatePoint`: an immutable DateTime implementation with stricter error handling and return types + * Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate + * Add `$modifier` argument to the `now()` helper + +6.3 +--- + + * Add `ClockAwareTrait` to help write time-sensitive classes + * Add `Clock` class and `now()` function + +6.2 +--- + + * Add the component diff --git a/data/web/inc/lib/vendor/symfony/clock/Clock.php b/data/web/inc/lib/vendor/symfony/clock/Clock.php new file mode 100644 index 000000000..311e8fc07 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/Clock.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface as PsrClockInterface; + +/** + * A global clock. + * + * @author Nicolas Grekas + */ +final class Clock implements ClockInterface +{ + private static ClockInterface $globalClock; + + public function __construct( + private readonly ?PsrClockInterface $clock = null, + private ?\DateTimeZone $timezone = null, + ) { + } + + /** + * Returns the current global clock. + * + * Note that you should prefer injecting a ClockInterface or using + * ClockAwareTrait when possible instead of using this method. + */ + public static function get(): ClockInterface + { + return self::$globalClock ??= new NativeClock(); + } + + public static function set(PsrClockInterface $clock): void + { + self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock); + } + + public function now(): DatePoint + { + $now = ($this->clock ?? self::get())->now(); + + if (!$now instanceof DatePoint) { + $now = DatePoint::createFromInterface($now); + } + + return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now; + } + + public function sleep(float|int $seconds): void + { + $clock = $this->clock ?? self::get(); + + if ($clock instanceof ClockInterface) { + $clock->sleep($seconds); + } else { + (new NativeClock())->sleep($seconds); + } + } + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->timezone = $timezone; + + return $clone; + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/ClockAwareTrait.php b/data/web/inc/lib/vendor/symfony/clock/ClockAwareTrait.php new file mode 100644 index 000000000..e723d7f86 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/ClockAwareTrait.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface; +use Symfony\Contracts\Service\Attribute\Required; + +/** + * A trait to help write time-sensitive classes. + * + * @author Nicolas Grekas + */ +trait ClockAwareTrait +{ + private readonly ClockInterface $clock; + + #[Required] + public function setClock(ClockInterface $clock): void + { + $this->clock = $clock; + } + + protected function now(): DatePoint + { + $now = ($this->clock ??= new Clock())->now(); + + return $now instanceof DatePoint ? $now : DatePoint::createFromInterface($now); + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/ClockInterface.php b/data/web/inc/lib/vendor/symfony/clock/ClockInterface.php new file mode 100644 index 000000000..435775a8f --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/ClockInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +use Psr\Clock\ClockInterface as PsrClockInterface; + +/** + * @author Nicolas Grekas + */ +interface ClockInterface extends PsrClockInterface +{ + public function sleep(float|int $seconds): void; + + public function withTimeZone(\DateTimeZone|string $timezone): static; +} diff --git a/data/web/inc/lib/vendor/symfony/clock/DatePoint.php b/data/web/inc/lib/vendor/symfony/clock/DatePoint.php new file mode 100644 index 000000000..86a8667c5 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/DatePoint.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * An immmutable DateTime with stricter error handling and return types than the native one. + * + * @author Nicolas Grekas + */ +final class DatePoint extends \DateTimeImmutable +{ + /** + * @throws \DateMalformedStringException When $datetime is invalid + */ + public function __construct(string $datetime = 'now', ?\DateTimeZone $timezone = null, ?parent $reference = null) + { + if (null === $reference) { + $clock = Clock::get(); + + // copying the clock's date into $this goes through its local time, which is ambiguous during the DST fall-back hour + if ('now' === $datetime && $clock instanceof NativeClock) { + parent::__construct('now', $timezone ?? $clock->now()->getTimezone()); + + return; + } + + $reference = $clock->now(); + } + + $now = $reference; + + if ('now' !== $datetime) { + if (!$now instanceof static) { + $now = static::createFromInterface($now); + } + + if (\PHP_VERSION_ID < 80300) { + try { + $builtInDate = new parent($datetime, $timezone ?? $now->getTimezone()); + $timezone = $builtInDate->getTimezone(); + } catch (\Exception $e) { + throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e); + } + } else { + $builtInDate = new parent($datetime, $timezone ?? $now->getTimezone()); + $timezone = $builtInDate->getTimezone(); + } + + $parsed = date_parse($datetime); + + if (false !== $parsed['year'] && false !== $parsed['month'] && false !== $parsed['day']) { + // the native result does not depend on the current time when $datetime has a full date + $now = $builtInDate; + } else { + $now = $now->setTimezone($timezone); + + // the native constructor starts from midnight when $datetime has a date but no time, modify() does not + if (false === $parsed['hour'] && (false !== $parsed['month'] || false !== $parsed['day'])) { + $datetime = 'midnight '.$datetime; + } elseif (false === $parsed['fraction'] && (false !== $parsed['year'] || false !== $parsed['hour'])) { + // it also resets the microseconds when $datetime sets a field without a fraction + $now = \PHP_INT_SIZE >= 8 ? $now->setTimestamp($now->getTimestamp()) : static::createFromFormat('U', $now->format('U'))->setTimezone($timezone); + } + + $now = $now->modify($datetime); + } + } elseif (null !== $timezone) { + $now = $now->setTimezone($timezone); + } + + $this->__unserialize((array) $now); + + // copying $now goes through its local time, which is ambiguous during the DST fall-back hour + if ($this != $now) { + // PHP picks the DST occurrence when the timezone is part of the string + $date = $now->format('Y-m-d H:i:s.u e'); + + if ($now == new parent($date)) { + parent::__construct($date); + } + } + } + + /** + * @throws \DateMalformedStringException When $format or $datetime are invalid + */ + public static function createFromFormat(string $format, string $datetime, ?\DateTimeZone $timezone = null): static + { + return parent::createFromFormat($format, $datetime, $timezone) ?: throw new \DateMalformedStringException(static::getLastErrors()['errors'][0] ?? 'Invalid date string or format.'); + } + + public static function createFromInterface(\DateTimeInterface $object): static + { + return parent::createFromInterface($object); + } + + public static function createFromMutable(\DateTime $object): static + { + return parent::createFromMutable($object); + } + + public static function createFromTimestamp(int|float $timestamp): static + { + if (\PHP_VERSION_ID >= 80400) { + return parent::createFromTimestamp($timestamp); + } + + if (\is_int($timestamp) || !$ms = (int) $timestamp - $timestamp) { + return static::createFromFormat('U', (string) $timestamp); + } + + if (!is_finite($timestamp) || \PHP_INT_MAX + 1.0 <= $timestamp || \PHP_INT_MIN > $timestamp) { + throw new \DateRangeError(\sprintf('DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %s and %s.999999, %s given', \PHP_INT_MIN, \PHP_INT_MAX, $timestamp)); + } + + if ($timestamp < 0) { + $timestamp = (int) $timestamp - 2.0 + $ms; + } + + return static::createFromFormat('U.u', \sprintf('%.6F', $timestamp)); + } + + public function add(\DateInterval $interval): static + { + return parent::add($interval); + } + + public function sub(\DateInterval $interval): static + { + return parent::sub($interval); + } + + /** + * @throws \DateMalformedStringException When $modifier is invalid + */ + public function modify(string $modifier): static + { + if (\PHP_VERSION_ID < 80300) { + return @parent::modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? \sprintf('Invalid modifier: "%s".', $modifier)); + } + + return parent::modify($modifier); + } + + public function setTimestamp(int $value): static + { + return parent::setTimestamp($value); + } + + public function setDate(int $year, int $month, int $day): static + { + return parent::setDate($year, $month, $day); + } + + public function setISODate(int $year, int $week, int $day = 1): static + { + return parent::setISODate($year, $week, $day); + } + + public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static + { + return parent::setTime($hour, $minute, $second, $microsecond); + } + + public function setTimezone(\DateTimeZone $timezone): static + { + return parent::setTimezone($timezone); + } + + public function getTimezone(): \DateTimeZone + { + return parent::getTimezone() ?: throw new \DateInvalidTimeZoneException('The DatePoint object has no timezone.'); + } + + public function setMicrosecond(int $microsecond): static + { + if ($microsecond < 0 || $microsecond > 999999) { + throw new \DateRangeError('DatePoint::setMicrosecond(): Argument #1 ($microsecond) must be between 0 and 999999, '.$microsecond.' given'); + } + + if (\PHP_VERSION_ID < 80400) { + return $this->setTime(...explode('.', $this->format('H.i.s.'.$microsecond))); + } + + return parent::setMicrosecond($microsecond); + } + + public function getMicrosecond(): int + { + if (\PHP_VERSION_ID >= 80400) { + return parent::getMicrosecond(); + } + + return $this->format('u'); + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE b/data/web/inc/lib/vendor/symfony/clock/LICENSE similarity index 95% rename from data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE rename to data/web/inc/lib/vendor/symfony/clock/LICENSE index 99c6bdf35..733c826eb 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/LICENSE +++ b/data/web/inc/lib/vendor/symfony/clock/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021-present Fabien Potencier +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/data/web/inc/lib/vendor/symfony/clock/MockClock.php b/data/web/inc/lib/vendor/symfony/clock/MockClock.php new file mode 100644 index 000000000..5c25e2d04 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/MockClock.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * A clock that always returns the same date, suitable for testing time-sensitive logic. + * + * Consider using ClockSensitiveTrait in your test cases instead of using this class directly. + * + * @author Nicolas Grekas + */ +final class MockClock implements ClockInterface +{ + private DatePoint $now; + + /** + * @throws \DateMalformedStringException When $now is invalid + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZone|string|null $timezone = null) + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + if (\is_string($now)) { + $now = new DatePoint($now, $timezone ?? new \DateTimeZone('UTC')); + } elseif (!$now instanceof DatePoint) { + $now = DatePoint::createFromInterface($now); + } + + $this->now = null !== $timezone ? $now->setTimezone($timezone) : $now; + } + + public function now(): DatePoint + { + return clone $this->now; + } + + public function sleep(float|int $seconds): void + { + if (0 >= $seconds) { + return; + } + + $s = (float) $this->now->format('U') + floor($seconds); + $us = (int) $this->now->format('u') + (int) round(fmod($seconds, 1) * 1e6); + + if (1000000 <= $us) { + ++$s; + $us -= 1000000; + } + + $this->now = DatePoint::createFromFormat('U.u', \sprintf('%.0F.%06d', $s, $us))->setTimezone($this->now->getTimezone()); + } + + /** + * @throws \DateMalformedStringException When $modifier is invalid + */ + public function modify(string $modifier): void + { + if (\PHP_VERSION_ID < 80300) { + $this->now = @$this->now->modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? \sprintf('Invalid modifier: "%s". Could not modify MockClock.', $modifier)); + + return; + } + + $this->now = $this->now->modify($modifier); + } + + /** + * @throws \DateInvalidTimeZoneException When the timezone name is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->now = $clone->now->setTimezone($timezone); + + return $clone; + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/MonotonicClock.php b/data/web/inc/lib/vendor/symfony/clock/MonotonicClock.php new file mode 100644 index 000000000..d27bf9c31 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/MonotonicClock.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * A monotonic clock suitable for performance profiling. + * + * @author Nicolas Grekas + */ +final class MonotonicClock implements ClockInterface +{ + private int $sOffset; + private int $usOffset; + private \DateTimeZone $timezone; + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function __construct(\DateTimeZone|string|null $timezone = null) + { + if (false === $offset = hrtime()) { + throw new \RuntimeException('hrtime() returned false: the runtime environment does not provide access to a monotonic timer.'); + } + + $time = explode(' ', microtime(), 2); + $this->sOffset = $time[1] - $offset[0]; + $this->usOffset = (int) ($time[0] * 1000000) - (int) ($offset[1] / 1000); + + $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; + } + + public function now(): DatePoint + { + [$s, $us] = hrtime(); + + if (1000000 <= $us = (int) ($us / 1000) + $this->usOffset) { + ++$s; + $us -= 1000000; + } elseif (0 > $us) { + --$s; + $us += 1000000; + } + + if (6 !== \strlen($now = (string) $us)) { + $now = str_pad($now, 6, '0', \STR_PAD_LEFT); + } + + $now = '@'.($s + $this->sOffset).'.'.$now; + + return DatePoint::createFromInterface(new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone); + } + + public function sleep(float|int $seconds): void + { + if (0 < $s = (int) $seconds) { + sleep($s); + } + + if (0 < $us = $seconds - $s) { + usleep((int) ($us * 1E6)); + } + } + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->timezone = $timezone; + + return $clone; + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/NativeClock.php b/data/web/inc/lib/vendor/symfony/clock/NativeClock.php new file mode 100644 index 000000000..b580a886c --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/NativeClock.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * A clock that relies the system time. + * + * @author Nicolas Grekas + */ +final class NativeClock implements ClockInterface +{ + private \DateTimeZone $timezone; + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function __construct(\DateTimeZone|string|null $timezone = null) + { + $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; + } + + public function now(): DatePoint + { + return DatePoint::createFromInterface(new \DateTimeImmutable('now', $this->timezone)); + } + + public function sleep(float|int $seconds): void + { + if (0 < $s = (int) $seconds) { + sleep($s); + } + + if (0 < $us = $seconds - $s) { + usleep((int) ($us * 1E6)); + } + } + + /** + * @throws \DateInvalidTimeZoneException When $timezone is invalid + */ + public function withTimeZone(\DateTimeZone|string $timezone): static + { + if (\PHP_VERSION_ID >= 80300 && \is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } elseif (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new \DateInvalidTimeZoneException($e->getMessage(), $e->getCode(), $e); + } + } + + $clone = clone $this; + $clone->timezone = $timezone; + + return $clone; + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/README.md b/data/web/inc/lib/vendor/symfony/clock/README.md new file mode 100644 index 000000000..e80b5d377 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/README.md @@ -0,0 +1,47 @@ +Clock Component +=============== + +Symfony Clock decouples applications from the system clock. + +Getting Started +--------------- + +```bash +composer require symfony/clock +``` + +```php +use Symfony\Component\Clock\NativeClock; +use Symfony\Component\Clock\ClockInterface; + +class MyClockSensitiveClass +{ + public function __construct( + private ClockInterface $clock, + ) { + // Only if you need to force a timezone: + //$this->clock = $clock->withTimeZone('UTC'); + } + + public function doSomething() + { + $now = $this->clock->now(); + // [...] do something with $now, which is a \DateTimeImmutable object + + $this->clock->sleep(2.5); // Pause execution for 2.5 seconds + } +} + +$clock = new NativeClock(); +$service = new MyClockSensitiveClass($clock); +$service->doSomething(); +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/clock.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/data/web/inc/lib/vendor/symfony/clock/Resources/now.php b/data/web/inc/lib/vendor/symfony/clock/Resources/now.php new file mode 100644 index 000000000..47d086c67 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/Resources/now.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +if (!\function_exists(now::class)) { + /** + * @throws \DateMalformedStringException When the modifier is invalid + */ + function now(string $modifier = 'now'): DatePoint + { + if ('now' !== $modifier) { + return new DatePoint($modifier); + } + + $now = Clock::get()->now(); + + return $now instanceof DatePoint ? $now : DatePoint::createFromInterface($now); + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/Test/ClockSensitiveTrait.php b/data/web/inc/lib/vendor/symfony/clock/Test/ClockSensitiveTrait.php new file mode 100644 index 000000000..f71f3a116 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/Test/ClockSensitiveTrait.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock\Test; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Clock\MockClock; + +use function Symfony\Component\Clock\now; + +/** + * Helps with mocking the time in your test cases. + * + * This trait provides one self::mockTime() method that freezes the time. + * It restores the global clock after each test case. + * self::mockTime() accepts either a string (eg '+1 days' or '2022-12-22'), + * a DateTimeImmutable, or a boolean (to freeze/restore the global clock). + * + * @author Nicolas Grekas + */ +trait ClockSensitiveTrait +{ + public static function mockTime(string|\DateTimeImmutable|bool $when = true): ClockInterface + { + Clock::set(match (true) { + false === $when => self::saveClockBeforeTest(false), + true === $when => new MockClock(), + $when instanceof \DateTimeImmutable => new MockClock($when), + default => new MockClock(now($when)), + }); + + return Clock::get(); + } + + /** + * @beforeClass + * + * @before + * + * @internal + */ + #[Before] + #[BeforeClass] + public static function saveClockBeforeTest(bool $save = true): ClockInterface + { + static $originalClock; + + if ($save && $originalClock) { + self::restoreClockAfterTest(); + } + + return $save ? $originalClock = Clock::get() : $originalClock; + } + + /** + * @after + * + * @internal + */ + #[After] + protected static function restoreClockAfterTest(): void + { + Clock::set(self::saveClockBeforeTest(false)); + } +} diff --git a/data/web/inc/lib/vendor/symfony/clock/composer.json b/data/web/inc/lib/vendor/symfony/clock/composer.json new file mode 100644 index 000000000..491215f6b --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/clock/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/clock", + "type": "library", + "description": "Decouples applications from the system clock", + "keywords": ["clock", "time", "psr20"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/clock-implementation": "1.0" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "autoload": { + "files": [ "Resources/now.php" ], + "psr-4": { "Symfony\\Component\\Clock\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json b/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json index ceb6c0796..9279eb1b0 100644 --- a/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json +++ b/data/web/inc/lib/vendor/symfony/deprecation-contracts/composer.json @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.7-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/data/web/inc/lib/vendor/symfony/polyfill-ctype/Ctype.php b/data/web/inc/lib/vendor/symfony/polyfill-ctype/Ctype.php index ba75a2c95..e26cc0221 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-ctype/Ctype.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-ctype/Ctype.php @@ -211,6 +211,10 @@ final class Ctype */ private static function convert_int_to_char_for_ctype($int, $function) { + if (\PHP_VERSION_ID >= 80100 && !\is_string($int)) { + @trigger_error($function.'(): Argument of type '.get_debug_type($int).' will be interpreted as string in the future', \E_USER_DEPRECATED); + } + if (!\is_int($int)) { return $int; } @@ -219,10 +223,6 @@ final class Ctype return (string) $int; } - if (\PHP_VERSION_ID >= 80100) { - @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); - } - if ($int < 0) { $int += 256; } diff --git a/data/web/inc/lib/vendor/symfony/polyfill-ctype/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-ctype/LICENSE index 3f853aaf3..7536caeae 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-ctype/LICENSE +++ b/data/web/inc/lib/vendor/symfony/polyfill-ctype/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2019 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/data/web/inc/lib/vendor/symfony/polyfill-ctype/README.md b/data/web/inc/lib/vendor/symfony/polyfill-ctype/README.md index 8add1ab00..b144d03c3 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-ctype/README.md +++ b/data/web/inc/lib/vendor/symfony/polyfill-ctype/README.md @@ -4,7 +4,7 @@ Symfony Polyfill / Ctype This component provides `ctype_*` functions to users who run php versions without the ctype extension. More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). License ======= diff --git a/data/web/inc/lib/vendor/symfony/polyfill-ctype/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-ctype/composer.json index ccb8e5703..131ca7adb 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-ctype/composer.json +++ b/data/web/inc/lib/vendor/symfony/polyfill-ctype/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -30,9 +30,6 @@ }, "minimum-stability": "dev", "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" diff --git a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Mbstring.php b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Mbstring.php index 2e0b96940..5a59d2690 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Mbstring.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -48,6 +48,11 @@ namespace Symfony\Polyfill\Mbstring; * - mb_strstr - Finds first occurrence of a string within another * - mb_strwidth - Return width of string * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase + * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string + * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string + * - mb_rtrim - Strip whitespace (or other characters) from the end of a string * * Not implemented: * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) @@ -74,12 +79,60 @@ final class Mbstring ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], ]; + // Encodings rejected by mb_ord() and mb_chr(), with their aliases + private const UNSUPPORTED_CODEPOINT_ENCODINGS = [ + 'BASE64' => 'BASE64', + 'UUENCODE' => 'UUENCODE', + 'X-UUENCODE' => 'UUENCODE', + 'HTML-ENTITIES' => 'HTML-ENTITIES', + 'HTML' => 'HTML-ENTITIES', + 'QUOTED-PRINTABLE' => 'Quoted-Printable', + 'QPRINT' => 'Quoted-Printable', + 'UTF-7' => 'UTF-7', + 'UTF7' => 'UTF-7', + 'UTF7-IMAP' => 'UTF7-IMAP', + 'MUTF-7' => 'UTF7-IMAP', + 'JIS' => 'JIS', + 'ISO-2022-JP' => 'ISO-2022-JP', + 'ISO-2022-JP-2004' => 'ISO-2022-JP-2004', + 'ISO-2022-JP-MOBILE#KDDI' => 'ISO-2022-JP-MOBILE#KDDI', + 'ISO-2022-JP-KDDI' => 'ISO-2022-JP-MOBILE#KDDI', + 'ISO-2022-JP-MS' => 'ISO-2022-JP-MS', + 'ISO2022JPMS' => 'ISO-2022-JP-MS', + 'CP50220' => 'CP50220', + 'CP50220RAW' => 'CP50220', + 'CP50220-RAW' => 'CP50220', + 'JIS-MS' => 'CP50220', + 'CP50221' => 'CP50221', + 'CP50222' => 'CP50222', + ]; + + // Well-formed characters in group 1, then the maximal subparts of ill-formed sequences + private const UTF8_CHAR_REGEX = '/([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})|\xE0[\xA0-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]|\xED[\x80-\x9F]|\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|[\x80-\xFF]/'; + private static $encodingList = ['ASCII', 'UTF-8']; private static $language = 'neutral'; private static $internalEncoding = 'UTF-8'; + private static $iconvSupportsIgnore; + private static $convertibleEncodings = ['UTF-8' => true]; public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { + if (\is_string($fromEncoding) && 2 < \strlen($fromEncoding) && '"' === $fromEncoding[0] && '"' === $fromEncoding[-1]) { + $fromEncoding = substr($fromEncoding, 1, -1); + } + + if (80000 <= \PHP_VERSION_ID + && (!isset(self::$convertibleEncodings[$toEncoding]) || (null !== $fromEncoding && !(\is_string($fromEncoding) && isset(self::$convertibleEncodings[$fromEncoding])))) + && null !== $error = self::getConversionError('mb_convert_encoding', 2, $toEncoding, $fromEncoding) + ) { + throw new \ValueError($error); + } + + if (\is_array($s)) { + return self::convertArray($s, $toEncoding, $fromEncoding); + } + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); } else { @@ -102,28 +155,84 @@ final class Mbstring $fromEncoding = 'Windows-1252'; } if ('UTF-8' !== $fromEncoding) { - $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + $s = self::iconv($fromEncoding, 'UTF-8', $s); + } elseif (!preg_match('//u', $s)) { + $s = self::scrubUtf8($s); } return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); } if ('HTML-ENTITIES' === $fromEncoding) { - $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $decodeControlChars = static function ($m) { + $code = '' !== ($m[2] ?? '') ? hexdec($m[2]) : (int) $m[1]; + + if ($code < 32 || 127 === $code) { + return \chr($code); + } + if (128 <= $code && $code <= 159) { + return "\xC2".\chr(0x80 | ($code & 0x3F)); + } + + return $m[0]; + }; + + if (\PHP_VERSION_ID >= 70400) { + $s = html_entity_decode($s, \ENT_QUOTES, 'UTF-8'); + // html_entity_decode() leaves numeric entities for C0/C1 control + // characters as-is (HTML spec), but mb_convert_encoding() decodes + // them. Catch what html_entity_decode() missed. + if (false !== strpos($s, '&#')) { + $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); + } + } else { + // PHP < 7.4: html_entity_decode() truncates strings at NUL bytes, + // so decode the control character entities first then call + // html_entity_decode() on each NUL-delimited chunk independently. + $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); + $s = implode("\0", array_map(static function ($chunk) { + return html_entity_decode($chunk, \ENT_QUOTES, 'UTF-8'); + }, explode("\0", $s))); + } $fromEncoding = 'UTF-8'; } - return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + if ('UTF-8' === $fromEncoding && !preg_match('//u', $s)) { + $s = self::scrubUtf8($s); + } + + return self::iconv($fromEncoding, $toEncoding, $s); } public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) { + if (\is_string($fromEncoding) && 2 < \strlen($fromEncoding) && '"' === $fromEncoding[0] && '"' === $fromEncoding[-1]) { + $fromEncoding = substr($fromEncoding, 1, -1); + } + + if (80000 <= \PHP_VERSION_ID && null !== $error = self::getConversionError('mb_convert_variables', 1, $toEncoding, $fromEncoding)) { + throw new \ValueError($error); + } + $ok = true; - array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { - if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { - $ok = false; + foreach ($vars as $i => &$var) { + $convert = static function (&$v) use (&$ok, $toEncoding, $fromEncoding, $i) { + if (\is_string($v)) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + } elseif (80600 <= \PHP_VERSION_ID && !\is_object($v)) { + trigger_error(\sprintf('mb_convert_variables(): Argument #%d must be of type string|array|object or only contain entries of type string|array|object, %s given', 3 + $i, strtok(get_debug_type($v), ' ')), \E_USER_WARNING); + } + }; + + if (\is_array($var)) { + array_walk_recursive($var, $convert); + } else { + $convert($var); } - }); + } + unset($var); return $ok ? $fromEncoding : false; } @@ -166,10 +275,10 @@ final class Mbstring if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = self::scrubUtf8($s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = self::iconv($encoding, 'UTF-8', $s); } $cnt = floor(\count($convmap) / 4) * 4; @@ -180,7 +289,7 @@ final class Mbstring $convmap[$i + 1] += $convmap[$i + 2]; } - $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) { $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; for ($i = 0; $i < $cnt; $i += 4) { if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { @@ -195,7 +304,7 @@ final class Mbstring return $s; } - return iconv('UTF-8', $encoding.'//IGNORE', $s); + return self::iconv('UTF-8', $encoding, $s); } public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) @@ -232,10 +341,10 @@ final class Mbstring if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = self::scrubUtf8($s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = self::iconv($encoding, 'UTF-8', $s); } static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; @@ -246,15 +355,20 @@ final class Mbstring $result = ''; while ($i < $len) { - $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"] ?? 1; $uchr = substr($s, $i, $ulen); $i += $ulen; - $c = self::mb_ord($uchr); + + // code points above U+10FFFF are outside any convmap, but mb_ord() rejects them + if (false === $c = self::mb_ord($uchr)) { + $result .= $uchr; + continue; + } for ($j = 0; $j < $cnt; $j += 4) { if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; - $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; continue 2; } } @@ -265,7 +379,7 @@ final class Mbstring return $result; } - return iconv('UTF-8', $encoding.'//IGNORE', $result); + return self::iconv('UTF-8', $encoding, $result); } public static function mb_convert_case($s, $mode, $encoding = null) @@ -280,10 +394,10 @@ final class Mbstring if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = self::scrubUtf8($s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = self::iconv($encoding, 'UTF-8', $s); } if (\MB_CASE_TITLE == $mode) { @@ -321,7 +435,7 @@ final class Mbstring $len = \strlen($s); while ($i < $len) { - $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"] ?? 1; $uchr = substr($s, $i, $ulen); $i += $ulen; @@ -347,7 +461,7 @@ final class Mbstring return $s; } - return iconv('UTF-8', $encoding.'//IGNORE', $s); + return self::iconv('UTF-8', $encoding, $s); } public static function mb_internal_encoding($encoding = null) @@ -368,7 +482,7 @@ final class Mbstring return false; } - throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); } public static function mb_language($lang = null) @@ -389,7 +503,7 @@ final class Mbstring return false; } - throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); } public static function mb_list_encodings() @@ -410,12 +524,6 @@ final class Mbstring public static function mb_check_encoding($var = null, $encoding = null) { - if (PHP_VERSION_ID < 70200 && \is_array($var)) { - trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING); - - return null; - } - if (null === $encoding) { if (null === $var) { return false; @@ -424,6 +532,10 @@ final class Mbstring } if (!\is_array($var)) { + if ('UTF-8' === self::getEncoding($encoding)) { + return (bool) preg_match('//u', $var); + } + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); } @@ -437,7 +549,6 @@ final class Mbstring } return true; - } public static function mb_detect_encoding($str, $encodingList = null, $strict = false) @@ -512,55 +623,29 @@ final class Mbstring return \strlen($s); } - return @iconv_strlen($s, $encoding); + if (false !== $len = @iconv_strlen($s, $encoding)) { + return $len; + } + + if ('UTF-8' !== $encoding) { + return $len; + } + + return preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]?|[\xE0-\xEF][\x80-\xBF]{0,2}|[\xF0-\xF7][\x80-\xBF]{0,3}|[\xF8-\xFB][\x80-\xBF]{0,4}|[\xFC-\xFD][\x80-\xBF]{0,5}|[\x80-\xBF\xFE\xFF]/s', $s); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strpos($haystack, $needle, $offset); - } - - $needle = (string) $needle; - if ('' === $needle) { - if (80000 > \PHP_VERSION_ID) { - trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); - - return false; - } - - return 0; - } - - return iconv_strpos($haystack, $needle, $offset, $encoding); + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, false, false); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - return strrpos($haystack, $needle, $offset); - } - if ($offset != (int) $offset) { $offset = 0; - } elseif ($offset = (int) $offset) { - if ($offset < 0) { - if (0 > $offset += self::mb_strlen($needle)) { - $haystack = self::mb_substr($haystack, 0, $offset, $encoding); - } - $offset = 0; - } else { - $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); - } } - $pos = '' !== $needle || 80000 > \PHP_VERSION_ID - ? iconv_strrpos($haystack, $needle, $encoding) - : self::mb_strlen($haystack, $encoding); - - return false !== $pos ? $offset + $pos : false; + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, true, false); } public static function mb_str_split($string, $split_length = 1, $encoding = null) @@ -581,17 +666,27 @@ final class Mbstring throw new \ValueError('Argument #2 ($length) must be greater than 0'); } + if (80300 <= \PHP_VERSION_ID && 0x3FFFFFFF < $split_length) { + throw new \ValueError('Argument #2 ($length) is too large'); + } + if (null === $encoding) { $encoding = mb_internal_encoding(); } if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $string = (string) $string; + + if (\strlen($string) <= $split_length) { + return '' === $string ? [] : [$string]; + } + $rx = '/('; while (65535 < $split_length) { $rx .= '.{65535}'; $split_length -= 65535; } - $rx .= '.{'.$split_length.'})/us'; + $rx .= '.{'.$split_length.'})/Aus'; return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } @@ -662,64 +757,32 @@ final class Mbstring public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { - [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ - self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), - self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), - ]); - - return self::mb_strpos($haystack, $needle, $offset, $encoding); + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, false, true); } public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) { - $pos = self::mb_stripos($haystack, $needle, 0, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, false, true); } public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) { - $encoding = self::getEncoding($encoding); - if ('CP850' === $encoding || 'ASCII' === $encoding) { - $pos = strrpos($haystack, $needle); - } else { - $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = iconv_strrpos($haystack, $needle, $encoding); - } - - return self::getSubpart($pos, $part, $haystack, $encoding); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, true, false); } public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) { - $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = self::mb_strripos($haystack, $needle, $encoding); - - return self::getSubpart($pos, $part, $haystack, $encoding); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, true, true); } public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { - $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); - $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); - - $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); - $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); - - return self::mb_strrpos($haystack, $needle, $offset, $encoding); + return self::find(__FUNCTION__, $haystack, $needle, $offset, $encoding, true, true); } public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) { - $pos = strpos($haystack, $needle); - if (false === $pos) { - return false; - } - if ($part) { - return substr($haystack, 0, $pos); - } - - return substr($haystack, $pos); + return self::findPart(__FUNCTION__, $haystack, $needle, $part, $encoding, false, false); } public static function mb_get_info($type = 'all') @@ -764,19 +827,40 @@ final class Mbstring public static function mb_strwidth($s, $encoding = null) { $encoding = self::getEncoding($encoding); - - if ('UTF-8' !== $encoding) { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + static $wideChars = null; + if (null === $wideChars) { + $wideChars = self::getData('eastAsianWidthRegexp'); } - $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + if ('UTF-8' !== $encoding) { + $s = self::iconv($encoding, 'UTF-8', $s); + } elseif (!preg_match('//u', $s)) { + // Each maximal subpart of an ill-formed sequence is one column wide + return preg_match_all(self::UTF8_CHAR_REGEX, $s) + preg_match_all($wideChars, self::scrubUtf8($s)); + } + + $s = preg_replace($wideChars, '', $s, -1, $wide); return ($wide << 1) + iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) { - return substr_count($haystack, $needle); + if ('' === $needle = (string) $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('mb_substr_count(): Empty substring', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_substr_count(): Argument #2 ($needle) must not be empty'); + } + + if (!$search = self::prepareSearch((string) $haystack, $needle, self::getEncoding($encoding), false, true)) { + return 0; + } + + return substr_count($search[0], $search[1]); } public static function mb_output_handler($contents, $status) @@ -786,6 +870,10 @@ final class Mbstring public static function mb_chr($code, $encoding = null) { + if (null !== $encoding && !self::assertCodepointEncoding($encoding, 'mb_chr')) { + return false; + } + if (0x80 > $code %= 0x200000) { $s = \chr($code); } elseif (0x800 > $code) { @@ -805,53 +893,107 @@ final class Mbstring public static function mb_ord($s, $encoding = null) { + if ('' === (string) $s) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('mb_ord(): Empty string', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_ord(): Argument #1 ($string) must not be empty'); + } + + if (null !== $encoding && !self::assertCodepointEncoding($encoding, 'mb_ord')) { + return false; + } + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { $s = mb_convert_encoding($s, 'UTF-8', $encoding); } - if (1 === \strlen($s)) { - return \ord($s); + $s = unpack('C*', substr($s, 0, 4)); + $code = $s[1] ?? 0; + if (0x80 > $code) { + return $code; } - $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; - if (0xF0 <= $code) { - return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; - } - if (0xE0 <= $code) { - return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; - } - if (0xC0 <= $code) { - return (($code - 0xC0) << 6) + $s[2] - 0x80; + if (0xC2 > $code || 0xF4 < $code) { + return false; } - return $code; + $c2 = $s[2] ?? 0; + if (0x80 !== ($c2 & 0xC0)) { + return false; + } + + if (0xE0 > $code) { + return (($code & 0x1F) << 6) | ($c2 & 0x3F); + } + + $c3 = $s[3] ?? 0; + if (0x80 !== ($c3 & 0xC0) + || 0xE0 === $code && 0xA0 > $c2 + || 0xED === $code && 0xA0 <= $c2 + ) { + return false; + } + + if (0xF0 > $code) { + return (($code & 0x0F) << 12) | (($c2 & 0x3F) << 6) | ($c3 & 0x3F); + } + + $c4 = $s[4] ?? 0; + if (0x80 !== ($c4 & 0xC0) + || 0xF0 === $code && 0x90 > $c2 + || 0xF4 === $code && 0x90 <= $c2 + ) { + return false; + } + + return (($code & 0x07) << 18) | (($c2 & 0x3F) << 12) | (($c3 & 0x3F) << 6) | ($c4 & 0x3F); } - public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string + /** @return string|false */ + public static function mb_scrub(?string $string, ?string $encoding = null): string { - if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { - throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); - } - if (null === $encoding) { $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_scrub(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return false; } - try { - $validEncoding = @self::mb_check_encoding('', $encoding); - } catch (\ValueError $e) { - throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); + return self::mb_convert_encoding((string) $string, $encoding, $encoding); + } + + /** @return string|false */ + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) { + return false; } - // BC for PHP 7.3 and lower - if (!$validEncoding) { - throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding)); - } + if (0 >= $padStringLength = self::mb_strlen($pad_string, $encoding)) { + if (\PHP_VERSION_ID < 80000) { + trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING); + + return false; + } - if (self::mb_strlen($pad_string, $encoding) <= 0) { throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); } + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + if (\PHP_VERSION_ID < 80000) { + trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + $paddingRequired = $length - self::mb_strlen($string, $encoding); if ($paddingRequired < 1) { @@ -860,27 +1002,352 @@ final class Mbstring switch ($pad_type) { case \STR_PAD_LEFT: - return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + $leftPaddingLength = $paddingRequired; + break; case \STR_PAD_RIGHT: - return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + $leftPaddingLength = 0; + break; default: - $leftPaddingLength = floor($paddingRequired / 2); - $rightPaddingLength = $paddingRequired - $leftPaddingLength; - - return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + $leftPaddingLength = intdiv($paddingRequired, 2); } + + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return str_repeat($pad_string, intdiv($leftPaddingLength, $padStringLength)).self::mb_substr($pad_string, 0, $leftPaddingLength % $padStringLength, $encoding) + .$string + .str_repeat($pad_string, intdiv($rightPaddingLength, $padStringLength)).self::mb_substr($pad_string, 0, $rightPaddingLength % $padStringLength, $encoding); } - private static function getSubpart($pos, $part, $haystack, $encoding) + /** @return string|false */ + public static function mb_ucfirst(string $string, ?string $encoding = null) { - if (false === $pos) { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { return false; } - if ($part) { - return self::mb_substr($haystack, 0, $pos, $encoding); + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + /** @return string|false */ + public static function mb_lcfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return false; } - return self::mb_substr($haystack, $pos, null, $encoding); + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + /** @return string|false */ + public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** + * Finds the position of $needle in $haystack like native mbstring. + * + * @return int|false + */ + private static function find(string $function, $haystack, $needle, $offset, $encoding, bool $reverse, bool $fold) + { + $needle = (string) $needle; + + if (80000 > \PHP_VERSION_ID && '' === $needle && 'mb_stripos' === $function) { + trigger_error($function.'(): Empty delimiter', \E_USER_WARNING); + + return false; + } + + if (!$search = self::prepareSearch((string) $haystack, $needle, self::searchEncoding($encoding, $fold), $fold, $fold)) { + return false; + } + + [$haystack, $needle, $bytes, $dropped] = $search; + + if (80000 > \PHP_VERSION_ID && $fold && ('' === $haystack || '' === $needle)) { + return false; + } + + if (false === $start = self::utf8Offset($haystack, $offset = (int) $offset, $bytes)) { + // Native mbstring counts the ill-formed sequences that iconv() dropped: the offset could be in range + if ($dropped && abs($offset) <= self::utf8Length($haystack) + $dropped) { + return false; + } + + if (80000 > \PHP_VERSION_ID) { + trigger_error($function.'(): '.($reverse ? 'Offset is greater than the length of haystack string' : 'Offset not contained in string'), \E_USER_WARNING); + + return false; + } + + throw new \ValueError($function.'(): Argument #3 ($offset) must be contained in argument #1 ($haystack)'); + } + + if (80000 > \PHP_VERSION_ID && '' === $needle) { + if ('mb_strpos' === $function) { + trigger_error($function.'(): Empty delimiter', \E_USER_WARNING); + } + + return false; + } + + if (!$reverse) { + $pos = strpos($haystack, $needle, $start); + } else { + $pos = strrpos($haystack, $needle, 0 > $offset ? $start - \strlen($haystack) : $start); + } + + return false === $pos || $bytes ? $pos : self::utf8Length(substr($haystack, 0, $pos)); + } + + /** + * Returns the part of $haystack before or from $needle like native mbstring. + * + * @return string|false + */ + private static function findPart(string $function, $haystack, $needle, $part, $encoding, bool $reverse, bool $fold) + { + $needle = (string) $needle; + + if (80000 > \PHP_VERSION_ID && '' === $needle) { + if (!$reverse) { + trigger_error($function.'(): Empty delimiter', \E_USER_WARNING); + } + + return false; + } + + $haystack = (string) $haystack; + + if (!$search = self::prepareSearch($haystack, $needle, $encoding = self::searchEncoding($encoding, $fold), $fold, $fold)) { + return false; + } + + [$h, $n, $bytes] = $search; + + if (false === $pos = $reverse ? strrpos($h, $n) : strpos($h, $n)) { + return false; + } + + if ($bytes) { + return (string) ($part ? substr($haystack, 0, $pos) : substr($haystack, $pos)); + } + + if ($fold || ('UTF-8' === $encoding && (!preg_match('//u', $haystack) || !preg_match('//u', $needle)))) { + // Cut after as many characters as native mbstring counts before the match + $pos = self::utf8Length(substr($h, 0, $pos)); + $h = 'UTF-8' === $encoding ? self::markIllFormedUtf8($haystack) : self::utf8($haystack, $encoding); + $pos = self::utf8Offset($h, $pos, false); + } + + $h = (string) ($part ? substr($h, 0, $pos) : substr($h, $pos)); + + if (false !== strpos($h, "\xFF")) { + $h = str_replace("\xFF", 'none' === mb_substitute_character() ? '' : '?', $h); + } + + return 'UTF-8' === $encoding ? $h : (string) @self::iconv('UTF-8', $encoding, $h); + } + + /** + * @return array{string, string, bool, int}|null The haystack and the needle to search, whether offsets in them count bytes rather than UTF-8 characters, and how many bytes of ill-formed sequences iconv() dropped from the haystack + */ + private static function prepareSearch(string $haystack, string $needle, string $encoding, bool $fold, bool $markIllFormed) + { + if ('ASCII' === $encoding || ('CP850' === $encoding && !$fold)) { + if ('ASCII' === $encoding) { + // Native mbstring turns every byte above 0x7F into the same error marker + $haystack = preg_replace('/[\x80-\xFF]/', "\xFF", $haystack); + $needle = preg_replace('/[\x80-\xFF]/', "\xFF", $needle); + } + + if ($fold) { + $haystack = strtr($haystack, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + $needle = strtr($needle, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + return [$haystack, $needle, true, 0]; + } + + $dropped = 0; + + if ('UTF-8' !== $encoding) { + if (false === ($haystack = self::utf8($haystack, $encoding, $dropped)) || false === $needle = self::utf8($needle, $encoding)) { + return null; + } + } elseif ($markIllFormed) { + $haystack = self::markIllFormedUtf8($haystack); + $needle = self::markIllFormedUtf8($needle); + } + + return $fold ? [self::foldCase($haystack), self::foldCase($needle), false, $dropped] : [$haystack, $needle, false, $dropped]; + } + + private static function searchEncoding($encoding, bool $fold): string + { + $normalizedEncoding = self::getEncoding($encoding); + + // Native mbstring folds the case of 8bit strings as Latin-1 + return $fold && 'CP850' === $normalizedEncoding && null !== $encoding && 'CP850' !== strtoupper($encoding) ? 'ISO-8859-1' : $normalizedEncoding; + } + + /** + * Converts $s to UTF-8 to search it, ending with "\xFF", a byte that is invalid in UTF-8, when its last sequence is truncated, like native mbstring. + * + * @param int $dropped Set to the number of bytes of the ill-formed sequences that iconv() dropped elsewhere + * + * @return string|false False when the encoding is unknown + */ + private static function utf8(string $s, string $encoding, &$dropped = 0) + { + if ('UTF-8' === $encoding || '' === $s) { + return $s; + } + + if (false !== $u = @iconv($encoding, 'UTF-8', $s)) { + return $u; + } + + // Let iconv() warn about an unknown encoding + if (false === iconv($encoding, 'UTF-8', '')) { + return false; + } + + // iconv() drops the ill-formed sequences, but fails on a truncated one at the end + for ($i = 0; $i < 4; ++$i) { + if (false !== $u = @self::iconv($encoding, 'UTF-8', $i ? substr($s, 0, -$i) : $s)) { + $dropped = \strlen($s) - $i - \strlen((string) @iconv('UTF-8', $encoding, $u)); + + return $i || '' === $u ? $u."\xFF" : $u; + } + } + + $dropped = \strlen($s); + + return "\xFF"; + } + + /** + * Replaces each maximal subpart of ill-formed UTF-8 sequences with "\xFF", like native mbstring before searching case-insensitively. + */ + private static function markIllFormedUtf8(string $s): string + { + if (preg_match('//u', $s)) { + return $s; + } + + return preg_replace('/\G(?:[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*+\K(?:\xE0[\xA0-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]|\xED[\x80-\x9F]|\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|[\x80-\xFF])/', "\xFF", $s); + } + + /** + * Folds the case of UTF-8 $s like the simple case folding of native mbstring. + */ + private static function foldCase(string $s): string + { + static $map = null; + + if (null === $map) { + $map = array_combine(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1]) + self::getData('lowerCase'); + unset($map["\xC4\xB0"]); // U+0130 has no simple case folding + } + + return strtr($s, $map); + } + + /** + * Counts the characters of UTF-8 $s like native mbstring, even when ill-formed: one per byte that is not a continuation byte. + */ + private static function utf8Length(string $s): int + { + return \strlen($s) - array_sum(\array_slice(count_chars($s), 0x80, 0x40)); + } + + /** + * @return int|false The offset in bytes of the character at $offset, false when it is out of range, like native mbstring + */ + private static function utf8Offset(string $s, int $offset, bool $bytes) + { + if (0 === $offset) { + return 0; + } + + if ($bytes || preg_match('//u', $u = str_replace("\xFF", '?', $s))) { + $length = $bytes ? \strlen($s) : self::utf8Length($s); + + if (0 > $offset) { + $offset += $length; + } + + if (0 > $offset || $offset > $length) { + return false; + } + + if ($bytes) { + return $offset; + } + + for ($rx = ''; 65535 < $offset; $offset -= 65535) { + $rx .= '.{65535}'; + } + preg_match('/^'.$rx.'.{'.$offset.'}\K/su', $u, $m, \PREG_OFFSET_CAPTURE); + + return $m[0][1]; + } + + // Walk ill-formed UTF-8 like native mbstring: backward over the bytes that are not continuation bytes, forward by the lengths that lead bytes announce + $length = \strlen($s); + + if (0 > $offset) { + $i = $length; + + while (0 > $offset) { + if (0 === $i) { + return false; + } + + if (0x80 !== (\ord($s[--$i]) & 0xC0)) { + ++$offset; + } + } + + return $i; + } + + $i = 0; + + while (0 < $offset--) { + if ($i >= $length) { + return false; + } + + $c = \ord($s[$i]); + $i += $c < 0xC2 ? 1 : ($c < 0xE0 ? 2 : ($c < 0xF0 ? 3 : ($c < 0xF5 ? 4 : 1))); + } + + return min($i, $length); } private static function html_encoding_callback(array $m) @@ -910,7 +1377,12 @@ final class Mbstring private static function title_case(array $s) { - return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + static $titleCase = null; + if (null === $titleCase) { + $titleCase = self::getData('titleCase'); + } + + return ($titleCase[$s[1]] ?? self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8')).self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); } private static function getData($file) @@ -942,6 +1414,249 @@ final class Mbstring return 'UTF-8'; } + if ('UTF-32' === $encoding) { + return 'UTF-32BE'; + } + + if ('UTF-16' === $encoding) { + return 'UTF-16BE'; + } + + if ('UCS-2' === $encoding) { + return 'UCS-2BE'; + } + return $encoding; } + + private static function iconv($fromEncoding, $toEncoding, $s) + { + if (null === self::$iconvSupportsIgnore) { + self::$iconvSupportsIgnore = false !== @iconv('UTF-8', 'UTF-8//IGNORE', ''); + } + + return self::$iconvSupportsIgnore + ? iconv($fromEncoding, $toEncoding.'//IGNORE', $s) + : iconv($fromEncoding, $toEncoding, $s); + } + + /** @return string|false */ + private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) { + return false; + } + + if ('' === $characters) { + return $string; + } + + if ('UTF-8' === self::getEncoding($encoding)) { + $encoding = null; + if (!preg_match('//u', $string) || (null !== $characters && !preg_match('//u', $characters))) { + return self::mb_trim_invalid_utf8($string, $characters, $function); + } + } else { + $string = self::iconv($encoding, 'UTF-8', $string); + + if (null !== $characters) { + $characters = self::iconv($encoding, 'UTF-8', $characters); + } + } + + if (null === $characters) { + $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; + } else { + $characters = preg_quote($characters); + } + + $string = preg_replace(\sprintf($regex, $characters), '', $string); + + if (null === $encoding) { + return $string; + } + + return self::iconv('UTF-8', $encoding, $string); + } + + /** + * Trims ill-formed UTF-8 like mbstring: each maximal subpart of an ill-formed + * sequence counts as one character, and the string is returned as is when + * nothing is trimmed, else re-encoded with the substitute character. + */ + private static function mb_trim_invalid_utf8(string $string, ?string $characters, string $function): string + { + $regex = self::UTF8_CHAR_REGEX; + + // Ill-formed sequences leave group 1 empty: they are trimmed when $characters has one too + preg_match_all($regex, $characters ?? "\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", $m); + $trimmed = array_flip($m[1]); + + $start = 0; + $end = \strlen($string); + + while ('mb_rtrim' !== $function && $start < $end && preg_match($regex, $string, $m, 0, $start) && isset($trimmed[$m[1] ?? ''])) { + $start += \strlen($m[0]); + } + + while ('mb_ltrim' !== $function && $start < $end) { + // Characters are at most 4 bytes long, and only continuation bytes don't start one + for ($i = $end - 1; $i > $start && $i > $end - 4 && 0x80 === (\ord($string[$i]) & 0xC0); --$i) { + } + + preg_match_all($regex, substr($string, $i, $end - $i), $m); + + if (!isset($trimmed[end($m[1])])) { + break; + } + + $end -= \strlen(end($m[0])); + } + + if (0 === $start && \strlen($string) === $end) { + return $string; + } + + $string = substr($string, $start, $end - $start); + + return 'none' === mb_substitute_character() ? self::scrubUtf8($string) : mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + } + + private static function scrubUtf8(string $s): string + { + return preg_replace(self::UTF8_CHAR_REGEX, '$1', $s); + } + + /** + * @param true[] $referenceIds The ids of the references followed to reach $array + */ + private static function convertArray(array $array, $toEncoding, $fromEncoding, array $referenceIds = []): array + { + $r = []; + + foreach ($array as $key => $v) { + if (null !== $v && !\is_scalar($v) && !\is_array($v)) { + trigger_error('mb_convert_encoding(): Object is not supported', \E_USER_WARNING); + continue; + } + + $k = \is_string($key) ? self::mb_convert_encoding($key, $toEncoding, $fromEncoding) : $key; + + if (\is_string($v)) { + $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding); + } elseif (\is_array($v)) { + // Only a reference can close a cycle: following the same one twice means one was found + $id = 70400 <= \PHP_VERSION_ID && ($reference = \ReflectionReference::fromArrayElement($array, $key)) ? $reference->getId() : null; + + if (null !== $id && isset($referenceIds[$id])) { + trigger_error('mb_convert_encoding(): Cannot convert recursively referenced values', \E_USER_WARNING); + $v = []; + } else { + $v = self::convertArray($v, $toEncoding, $fromEncoding, null === $id ? $referenceIds : $referenceIds + [$id => true]); + } + } + + // Keys that collide once converted keep their first value + $r += [$k => $v]; + } + + return $r; + } + + /** + * Returns the error native mbstring throws for these encoding arguments, if any. + */ + private static function getConversionError(string $function, int $argument, $toEncoding, $fromEncoding): ?string + { + if (!self::isConvertibleEncoding((string) $toEncoding)) { + return \sprintf('%s(): Argument #%d ($to_encoding) must be a valid encoding, "%s" given', $function, $argument, $toEncoding); + } + + if (null === $fromEncoding) { + return null; + } + + if (!$list = \is_array($fromEncoding) ? $fromEncoding : ('' === $fromEncoding ? [] : explode(',', $fromEncoding))) { + return \sprintf('%s(): Argument #%d ($from_encoding) must specify at least one encoding', $function, 1 + $argument); + } + + foreach ($list as $encoding) { + $encoding = \is_array($fromEncoding) ? (string) $encoding : trim($encoding, " \t"); + + // PHP 8.4 takes an empty entry of a comma-separated list for "auto" + if ('' === $encoding && !\is_array($fromEncoding) && 80400 <= \PHP_VERSION_ID) { + continue; + } + + if (0 !== strcasecmp($encoding, 'auto') && !self::isConvertibleEncoding($encoding)) { + return \sprintf('%s(): Argument #%d ($from_encoding) contains invalid encoding "%s"', $function, 1 + $argument, $encoding); + } + } + + return null; + } + + private static function isConvertibleEncoding(string $encoding): bool + { + if (isset(self::$convertibleEncodings[$encoding])) { + return true; + } + + $normalizedEncoding = self::getEncoding($encoding); + + // Native encoding names use no other characters, while iconv() also accepts suffixes like //TRANSLIT + if ('' === $normalizedEncoding || preg_match('/[^\w#\-.:]/', $encoding) || (null === (self::UNSUPPORTED_CODEPOINT_ENCODINGS[$normalizedEncoding] ?? null) && false === @iconv($normalizedEncoding, $normalizedEncoding, ''))) { + return false; + } + + return self::$convertibleEncodings[$encoding] = true; + } + + private static function assertEncoding(string $encoding, string $errorFormat): bool + { + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + + if (!$validEncoding) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING); + } else { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + } + + return $validEncoding; + } + + private static function assertCodepointEncoding(string $encoding, string $function): bool + { + if (null !== $name = self::UNSUPPORTED_CODEPOINT_ENCODINGS[strtoupper($encoding)] ?? null) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('%s(): Unsupported encoding "%s"', $function, $name), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('%s() does not support the "%s" encoding', $function, $name)); + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, '')) { + return true; + } + + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('%s(): Unknown encoding "%s"', $function, $encoding), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('%s(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $function, $encoding)); + } } diff --git a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Resources/unidata/eastAsianWidthRegexp.php b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Resources/unidata/eastAsianWidthRegexp.php new file mode 100644 index 000000000..34d875e52 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/Resources/unidata/eastAsianWidthRegexp.php @@ -0,0 +1,5 @@ + 'Dž', + 'Dž' => 'Dž', + 'dž' => 'Dž', + 'LJ' => 'Lj', + 'Lj' => 'Lj', + 'lj' => 'Lj', + 'NJ' => 'Nj', + 'Nj' => 'Nj', + 'nj' => 'Nj', + 'DZ' => 'Dz', + 'Dz' => 'Dz', + 'dz' => 'Dz', + 'ა' => 'ა', + 'ბ' => 'ბ', + 'გ' => 'გ', + 'დ' => 'დ', + 'ე' => 'ე', + 'ვ' => 'ვ', + 'ზ' => 'ზ', + 'თ' => 'თ', + 'ი' => 'ი', + 'კ' => 'კ', + 'ლ' => 'ლ', + 'მ' => 'მ', + 'ნ' => 'ნ', + 'ო' => 'ო', + 'პ' => 'პ', + 'ჟ' => 'ჟ', + 'რ' => 'რ', + 'ს' => 'ს', + 'ტ' => 'ტ', + 'უ' => 'უ', + 'ფ' => 'ფ', + 'ქ' => 'ქ', + 'ღ' => 'ღ', + 'ყ' => 'ყ', + 'შ' => 'შ', + 'ჩ' => 'ჩ', + 'ც' => 'ც', + 'ძ' => 'ძ', + 'წ' => 'წ', + 'ჭ' => 'ჭ', + 'ხ' => 'ხ', + 'ჯ' => 'ჯ', + 'ჰ' => 'ჰ', + 'ჱ' => 'ჱ', + 'ჲ' => 'ჲ', + 'ჳ' => 'ჳ', + 'ჴ' => 'ჴ', + 'ჵ' => 'ჵ', + 'ჶ' => 'ჶ', + 'ჷ' => 'ჷ', + 'ჸ' => 'ჸ', + 'ჹ' => 'ჹ', + 'ჺ' => 'ჺ', + 'ჽ' => 'ჽ', + 'ჾ' => 'ჾ', + 'ჿ' => 'ჿ', + 'ß' => 'Ss', + 'ff' => 'Ff', + 'fi' => 'Fi', + 'fl' => 'Fl', + 'ffi' => 'Ffi', + 'ffl' => 'Ffl', + 'ſt' => 'St', + 'st' => 'St', + 'և' => 'Եւ', + 'ﬓ' => 'Մն', + 'ﬔ' => 'Մե', + 'ﬕ' => 'Մի', + 'ﬖ' => 'Վն', + 'ﬗ' => 'Մխ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾳ' => 'ᾼ', + 'ᾼ' => 'ᾼ', + 'ῃ' => 'ῌ', + 'ῌ' => 'ῌ', + 'ῳ' => 'ῼ', + 'ῼ' => 'ῼ', + 'ᾲ' => 'Ὰͅ', + 'ᾴ' => 'Άͅ', + 'ῂ' => 'Ὴͅ', + 'ῄ' => 'Ήͅ', + 'ῲ' => 'Ὼͅ', + 'ῴ' => 'Ώͅ', + 'ᾷ' => 'ᾼ͂', + 'ῇ' => 'ῌ͂', + 'ῷ' => 'ῼ͂', +); diff --git a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap.php index ecf1a0352..0e934d7af 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -9,143 +9,8 @@ * file that was distributed with this source code. */ -use Symfony\Polyfill\Mbstring as p; - if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; } -if (!function_exists('mb_convert_encoding')) { - function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } -} -if (!function_exists('mb_decode_mimeheader')) { - function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } -} -if (!function_exists('mb_encode_mimeheader')) { - function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } -} -if (!function_exists('mb_decode_numericentity')) { - function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } -} -if (!function_exists('mb_encode_numericentity')) { - function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } -} -if (!function_exists('mb_convert_case')) { - function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } -} -if (!function_exists('mb_internal_encoding')) { - function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } -} -if (!function_exists('mb_language')) { - function mb_language($language = null) { return p\Mbstring::mb_language($language); } -} -if (!function_exists('mb_list_encodings')) { - function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } -} -if (!function_exists('mb_encoding_aliases')) { - function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } -} -if (!function_exists('mb_check_encoding')) { - function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } -} -if (!function_exists('mb_detect_encoding')) { - function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } -} -if (!function_exists('mb_detect_order')) { - function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } -} -if (!function_exists('mb_parse_str')) { - function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } -} -if (!function_exists('mb_strlen')) { - function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } -} -if (!function_exists('mb_strpos')) { - function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } -} -if (!function_exists('mb_strtolower')) { - function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } -} -if (!function_exists('mb_strtoupper')) { - function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } -} -if (!function_exists('mb_substitute_character')) { - function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } -} -if (!function_exists('mb_substr')) { - function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } -} -if (!function_exists('mb_stripos')) { - function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } -} -if (!function_exists('mb_stristr')) { - function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } -} -if (!function_exists('mb_strrchr')) { - function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } -} -if (!function_exists('mb_strrichr')) { - function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } -} -if (!function_exists('mb_strripos')) { - function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } -} -if (!function_exists('mb_strrpos')) { - function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } -} -if (!function_exists('mb_strstr')) { - function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } -} -if (!function_exists('mb_get_info')) { - function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } -} -if (!function_exists('mb_http_output')) { - function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } -} -if (!function_exists('mb_strwidth')) { - function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } -} -if (!function_exists('mb_substr_count')) { - function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } -} -if (!function_exists('mb_output_handler')) { - function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } -} -if (!function_exists('mb_http_input')) { - function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } -} - -if (!function_exists('mb_convert_variables')) { - function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } -} - -if (!function_exists('mb_ord')) { - function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } -} -if (!function_exists('mb_chr')) { - function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } -} -if (!function_exists('mb_scrub')) { - function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } -} -if (!function_exists('mb_str_split')) { - function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } -} - -if (!function_exists('mb_str_pad')) { - function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } -} - -if (extension_loaded('mbstring')) { - return; -} - -if (!defined('MB_CASE_UPPER')) { - define('MB_CASE_UPPER', 0); -} -if (!defined('MB_CASE_LOWER')) { - define('MB_CASE_LOWER', 1); -} -if (!defined('MB_CASE_TITLE')) { - define('MB_CASE_TITLE', 2); -} +return require __DIR__.'/bootstrap72.php'; diff --git a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap72.php b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap72.php new file mode 100644 index 000000000..2e3a06def --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap72.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { return p\Mbstring::mb_scrub($string, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + /** @return string|false */ + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + /** @return string|false */ + function mb_ucfirst(?string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + /** @return string|false */ + function mb_lcfirst(?string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_trim')) { + /** @return string|false */ + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + /** @return string|false */ + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + /** @return string|false */ + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap80.php b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap80.php index 2f9fb5b42..5fde10e83 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap80.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -93,7 +93,7 @@ if (!function_exists('mb_strstr')) { function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } } if (!function_exists('mb_get_info')) { - function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } + function mb_get_info(?string $type = 'all'): array|string|int|false|null { return p\Mbstring::mb_get_info((string) $type); } } if (!function_exists('mb_http_output')) { function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } @@ -122,14 +122,34 @@ if (!function_exists('mb_chr')) { function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } } if (!function_exists('mb_scrub')) { - function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } + function mb_scrub(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_scrub($string, $encoding); } } if (!function_exists('mb_str_split')) { function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } } if (!function_exists('mb_str_pad')) { - function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); } + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); } } if (extension_loaded('mbstring')) { diff --git a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/composer.json index bd99d4b9d..daa07f86b 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-mbstring/composer.json +++ b/data/web/inc/lib/vendor/symfony/polyfill-mbstring/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": ">=7.1" + "php": ">=7.2", + "ext-iconv": "*" }, "provide": { "ext-mbstring": "*" diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php80/Php80.php b/data/web/inc/lib/vendor/symfony/polyfill-php80/Php80.php index 362dd1a95..68fac391e 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-php80/Php80.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-php80/Php80.php @@ -60,7 +60,7 @@ final class Php80 public static function get_resource_id($res): int { if (!\is_resource($res) && null === @get_resource_type($res)) { - throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + throw new \TypeError(\sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); } return (int) $res; diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php80/PhpToken.php b/data/web/inc/lib/vendor/symfony/polyfill-php80/PhpToken.php index fe6e69105..0f6b65e36 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-php80/PhpToken.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-php80/PhpToken.php @@ -29,7 +29,7 @@ class PhpToken implements \Stringable public $text; /** - * @var int + * @var -1|positive-int */ public $line; @@ -38,6 +38,9 @@ class PhpToken implements \Stringable */ public $pos; + /** + * @param -1|positive-int $line + */ public function __construct(int $id, string $text, int $line = -1, int $position = -1) { $this->id = $id; @@ -48,11 +51,11 @@ class PhpToken implements \Stringable public function getTokenName(): ?string { - if ('UNKNOWN' === $name = token_name($this->id)) { - $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + if ($this->id < 256) { + return \chr($this->id & 0xFF); } - return $name; + return 'UNKNOWN' === ($name = token_name($this->id)) ? null : $name; } /** @@ -80,7 +83,7 @@ class PhpToken implements \Stringable } /** - * @return static[] + * @return list */ public static function tokenize(string $code, int $flags = 0): array { @@ -89,13 +92,15 @@ class PhpToken implements \Stringable $tokens = token_get_all($code, $flags); foreach ($tokens as $index => $token) { if (\is_string($token)) { - $id = \ord($token); + // b" is the only such token longer than one char + $id = \ord($token[-1]); $text = $token; } else { [$id, $text, $line] = $token; } $tokens[$index] = new static($id, $text, $line, $position); $position += \strlen($text); + $line += preg_match_all('/\r\n?|\n/', $text); } return $tokens; diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php80/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-php80/composer.json index 46ccde203..a503b039a 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-php80/composer.json +++ b/data/web/inc/lib/vendor/symfony/polyfill-php80/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=7.1" + "php": ">=7.2" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php deleted file mode 100644 index f0507b765..000000000 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/Php81.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Php81; - -/** - * @author Nicolas Grekas - * - * @internal - */ -final class Php81 -{ - public static function array_is_list(array $array): bool - { - if ([] === $array || $array === array_values($array)) { - return true; - } - - $nextKey = -1; - - foreach ($array as $k => $v) { - if ($k !== ++$nextKey) { - return false; - } - } - - return true; - } -} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/README.md b/data/web/inc/lib/vendor/symfony/polyfill-php81/README.md deleted file mode 100644 index c07ef7820..000000000 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/README.md +++ /dev/null @@ -1,18 +0,0 @@ -Symfony Polyfill / Php81 -======================== - -This component provides features added to PHP 8.1 core: - -- [`array_is_list`](https://php.net/array_is_list) -- [`enum_exists`](https://php.net/enum-exists) -- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant -- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types) -- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used) - -More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). - -License -======= - -This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php deleted file mode 100644 index 5ff93fcaf..000000000 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) { - /** - * @property string $data - */ - class CURLStringFile extends CURLFile - { - private $data; - - public function __construct(string $data, string $postname, string $mime = 'application/octet-stream') - { - $this->data = $data; - parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname); - } - - public function __set(string $name, $value): void - { - if ('data' !== $name) { - $this->$name = $value; - - return; - } - - if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) { - throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string'); - } - - $this->name = 'data://application/octet-stream;base64,'.base64_encode($value); - } - - public function __isset(string $name): bool - { - return isset($this->$name); - } - - public function &__get(string $name) - { - return $this->$name; - } - } -} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php deleted file mode 100644 index 9f872e02f..000000000 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/bootstrap.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Php81 as p; - -if (\PHP_VERSION_ID >= 80100) { - return; -} - -if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) { - define('MYSQLI_REFRESH_REPLICA', 64); -} - -if (!function_exists('array_is_list')) { - function array_is_list(array $array): bool { return p\Php81::array_is_list($array); } -} - -if (!function_exists('enum_exists')) { - function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; } -} diff --git a/data/web/inc/lib/vendor/ralouphie/getallheaders/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-php82/LICENSE similarity index 86% rename from data/web/inc/lib/vendor/ralouphie/getallheaders/LICENSE rename to data/web/inc/lib/vendor/symfony/polyfill-php82/LICENSE index be5540c2a..733c826eb 100644 --- a/data/web/inc/lib/vendor/ralouphie/getallheaders/LICENSE +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/LICENSE @@ -1,13 +1,11 @@ -The MIT License (MIT) - -Copyright (c) 2014 Ralph Khattar +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -17,5 +15,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/NoDynamicProperties.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/NoDynamicProperties.php new file mode 100644 index 000000000..450deff45 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/NoDynamicProperties.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @internal + */ +trait NoDynamicProperties +{ + public function __set(string $name, $value): void + { + throw new \Error('Cannot create dynamic property '.self::class.'::$'.$name); + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Php82.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Php82.php new file mode 100644 index 000000000..774ab3a84 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Php82.php @@ -0,0 +1,399 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @author Alexander M. Turek + * @author Greg Roach + * + * @internal + */ +class Php82 +{ + /** + * Determines if a string matches the ODBC quoting rules. + * + * A valid quoted string begins with a '{', ends with a '}', and has no '}' + * inside of the string that aren't repeated (as to be escaped). + * + * These rules are what .NET also follows. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/main/php_odbc_utils.c#L31-L57 + */ + public static function odbc_connection_string_is_quoted(string $str): bool + { + if ('' === $str || '{' !== $str[0]) { + return false; + } + + /* Check for } that aren't doubled up or at the end of the string */ + $length = \strlen($str) - 1; + for ($i = 0; $i < $length; ++$i) { + if ('}' !== $str[$i]) { + continue; + } + + if ('}' !== $str[++$i]) { + return false; + } + } + + return true; + } + + /** + * Determines if a value for a connection string should be quoted. + * + * The ODBC specification mentions: + * "Because of connection string and initialization file grammar, keywords and + * attribute values that contain the characters []{}(),;?*=!@ not enclosed + * with braces should be avoided." + * + * Note that it assumes that the string is *not* already quoted. You should + * check beforehand. + * + * @see https://github.com/php/php-src/blob/838f6bffff6363a204a2597cbfbaad1d7ee3f2b6/main/php_odbc_utils.c#L59-L73 + */ + public static function odbc_connection_string_should_quote(string $str): bool + { + return false !== strpbrk($str, '[]{}(),;?*=!@ '); + } + + public static function odbc_connection_string_quote(string $str): string + { + return '{'.str_replace('}', '}}', $str).'}'; + } + + /** + * Implementation closely based on the original C code - including the GOTOs + * and pointer-style string access. + * + * @see https://github.com/php/php-src/blob/master/Zend/zend_ini.c + */ + public static function ini_parse_quantity(string $value): int + { + // Avoid dependency on ctype_space() + $ctype_space = " \t\v\r\n\f"; + + $str = 0; + $str_end = \strlen($value); + $digits = $str; + $overflow = false; + + /* Ignore leading whitespace, but keep it for error messages. */ + while ($digits < $str_end && false !== strpos($ctype_space, $value[$digits])) { + ++$digits; + } + + /* Ignore trailing whitespace, but keep it for error messages. */ + while ($digits < $str_end && false !== strpos($ctype_space, $value[$str_end - 1])) { + --$str_end; + } + + if ($digits === $str_end) { + return 0; + } + + $is_negative = false; + + if ('+' === $value[$digits]) { + ++$digits; + } elseif ('-' === $value[$digits]) { + $is_negative = true; + ++$digits; + } + + if ($digits === $str_end || $value[$digits] < '0' || $value[$digits] > '9') { + $message = \sprintf( + 'Invalid quantity "%s": no valid leading digits, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + $base = 10; + $allowed_digits = '0123456789'; + + if ('0' === $value[$digits] && ($digits + 1 === $str_end || false === strpos($allowed_digits, $value[$digits + 1]))) { + if ($digits + 1 === $str_end) { + return 0; + } + + switch ($value[$digits + 1]) { + case 'g': + case 'G': + case 'm': + case 'M': + case 'k': + case 'K': + goto evaluation; + case 'x': + case 'X': + $base = 16; + $allowed_digits = '0123456789abcdefABCDEF'; + break; + case 'o': + case 'O': + $base = 8; + $allowed_digits = '01234567'; + break; + case 'b': + case 'B': + $base = 2; + $allowed_digits = '01'; + break; + default: + $message = \sprintf( + 'Invalid prefix "0%s", interpreting as "0" for backwards compatibility', + $value[$digits + 1] + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + $digits += 2; + if ($digits === $str_end) { + $message = \sprintf( + 'Invalid quantity "%s": no digits after base prefix, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + $digits_consumed = $digits; + /* Ignore leading whitespace. */ + while ($digits_consumed < $str_end && false !== strpos($ctype_space, $value[$digits_consumed])) { + ++$digits_consumed; + } + if ($digits_consumed !== $str_end && ('+' === $value[$digits_consumed] || '-' === $value[$digits_consumed])) { + ++$digits_consumed; + } + + if ($digits_consumed + 1 < $str_end && '0' === $value[$digits_consumed]) { + switch ($value[$digits_consumed + 1]) { + case 'x': + case 'X': + case 'o': + case 'O': + $digits_consumed += 2; + break; + case 'b': + case 'B': + // 0b is a valid pair of digits in base 16 + if (16 !== $base) { + $digits_consumed += 2; + } + break; + } + } + + if ($digits !== $digits_consumed) { + $message = \sprintf( + 'Invalid quantity "%s": no digits after base prefix, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + } + + evaluation: + + if (10 === $base && '0' === $value[$digits]) { + $base = 8; + $allowed_digits = '01234567'; + } + + while ($digits < $str_end && ' ' === $value[$digits]) { + ++$digits; + } + + if ($digits < $str_end && '+' === $value[$digits]) { + ++$digits; + } elseif ($digits < $str_end && '-' === $value[$digits]) { + $is_negative = true; + $overflow = true; + ++$digits; + } + + $digits_end = $digits; + + while ($digits_end < $str_end && false !== strpos($allowed_digits, $value[$digits_end])) { + ++$digits_end; + } + + // Mimic strtoul() on a zend_ulong using two half-width limbs, as PHP has no unsigned integers + $bits = \PHP_INT_SIZE << 2; + $mask = (1 << $bits) - 1; + $high = $low = 0; + + for ($i = $digits; $i < $digits_end; ++$i) { + $low = $low * $base + hexdec($value[$i]); + $high = $high * $base + ($low >> $bits); + $low &= $mask; + + if ($high > $mask) { + break; + } + } + + if ($high > $mask) { + $retval = -1; + $overflow = true; + } else { + $retval = ($high << $bits) | $low; + + if ($is_negative && 0 === $retval) { + $is_negative = false; + $overflow = false; + } elseif (0 > $retval) { + // -PHP_INT_MIN is PHP_INT_MIN itself + $overflow = $overflow || !$is_negative || \PHP_INT_MIN !== $retval; + } elseif ($is_negative) { + $retval = -$retval; + } + } + + if ($digits_end === $digits) { + $message = \sprintf( + 'Invalid quantity "%s": no valid leading digits, interpreting as "0" for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + + return 0; + } + + /* Allow for whitespace between integer portion and any suffix character */ + while ($digits_end < $str_end && false !== strpos($ctype_space, $value[$digits_end])) { + ++$digits_end; + } + + /* No exponent suffix. */ + if ($digits_end === $str_end) { + goto end; + } + + switch ($value[$str_end - 1]) { + case 'g': + case 'G': + $shift = 30; + break; + case 'm': + case 'M': + $shift = 20; + break; + case 'k': + case 'K': + $shift = 10; + break; + default: + /* Unknown suffix */ + $invalid = self::escapeString($value); + $interpreted = self::escapeString(substr($value, $str, $digits_end - $str)); + $chr = self::escapeString($value[$str_end - 1]); + + $message = \sprintf( + 'Invalid quantity "%s": unknown multiplier "%s", interpreting as "%s" for backwards compatibility', + $invalid, + $chr, + $interpreted + ); + + trigger_error($message, \E_USER_WARNING); + + return $retval; + } + + $factor = 1 << $shift; + + if (!$overflow) { + if ($retval > 0) { + $overflow = $retval > \PHP_INT_MAX / $factor; + } else { + $overflow = $retval < \PHP_INT_MIN / $factor; + } + } + + if (\is_float($retval * $factor)) { + $overflow = true; + $retval <<= $shift; + } else { + $retval *= $factor; + } + + if ($digits_end !== $str_end - 1) { + /* More than one character in suffix */ + $message = \sprintf( + 'Invalid quantity "%s", interpreting as "%s%s" for backwards compatibility', + self::escapeString($value), + self::escapeString(substr($value, $str, $digits_end - $str)), + self::escapeString($value[$str_end - 1]) + ); + trigger_error($message, \E_USER_WARNING); + + return $retval; + } + + end: + + if ($overflow) { + /* Not specifying the resulting value here because the caller may make + * additional conversions. Not specifying the allowed range + * because the caller may do narrower range checks. */ + $message = \sprintf( + 'Invalid quantity "%s": value is out of range, using overflow result for backwards compatibility', + self::escapeString($value) + ); + trigger_error($message, \E_USER_WARNING); + } + + return $retval; + } + + /** + * Escape the string to avoid null bytes and to make non-printable chars visible. + */ + private static function escapeString(string $string): string + { + $escaped = ''; + + for ($n = 0, $len = \strlen($string); $n < $len; ++$n) { + $c = \ord($string[$n]); + + if ($c < 32 || '\\' === $string[$n] || $c > 126) { + switch ($string[$n]) { + case "\n": $escaped .= '\\n'; break; + case "\r": $escaped .= '\\r'; break; + case "\t": $escaped .= '\\t'; break; + case "\f": $escaped .= '\\f'; break; + case "\v": $escaped .= '\\v'; break; + case '\\': $escaped .= '\\\\'; break; + case "\x1B": $escaped .= '\\e'; break; + default: + $escaped .= '\\x'.strtoupper(\sprintf('%02x', $c)); + } + } else { + $escaped .= $string[$n]; + } + } + + return $escaped; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/README.md b/data/web/inc/lib/vendor/symfony/polyfill-php82/README.md new file mode 100644 index 000000000..b3191557a --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/README.md @@ -0,0 +1,23 @@ +Symfony Polyfill / Php82 +======================== + +This component provides features added to PHP 8.2 core: + +- [`AllowDynamicProperties`](https://wiki.php.net/rfc/deprecate_dynamic_properties) +- [`SensitiveParameter`](https://wiki.php.net/rfc/redact_parameters_in_back_traces) +- [`SensitiveParameterValue`](https://wiki.php.net/rfc/redact_parameters_in_back_traces) +- [`Random\Engine`](https://wiki.php.net/rfc/rng_extension) +- [`Random\Engine\CryptoSafeEngine`](https://wiki.php.net/rfc/rng_extension) +- [`Random\Engine\Secure`](https://wiki.php.net/rfc/rng_extension) (check [arokettu/random-polyfill](https://packagist.org/packages/arokettu/random-polyfill) for more engines) +- [`odbc_connection_string_is_quoted()`](https://php.net/odbc_connection_string_is_quoted) +- [`odbc_connection_string_should_quote()`](https://php.net/odbc_connection_string_should_quote) +- [`odbc_connection_string_quote()`](https://php.net/odbc_connection_string_quote) +- [`ini_parse_quantity()`](https://php.net/ini_parse_quantity) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Random/Engine/Secure.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Random/Engine/Secure.php new file mode 100644 index 000000000..5565386c2 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Random/Engine/Secure.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82\Random\Engine; + +use Random\RandomException; +use Symfony\Polyfill\Php82\NoDynamicProperties; + +/** + * @author Tim Düsterhus + * @author Anton Smirnov + * + * @internal + */ +class Secure +{ + use NoDynamicProperties; + + public function generate(): string + { + try { + return random_bytes(\PHP_INT_SIZE); + } catch (\Exception $e) { + throw new RandomException($e->getMessage(), $e->getCode(), $e->getPrevious()); + } + } + + public function __sleep(): array + { + throw new \Exception("Serialization of 'Random\Engine\Secure' is not allowed"); + } + + public function __wakeup(): void + { + throw new \Exception("Unserialization of 'Random\Engine\Secure' is not allowed"); + } + + public function __clone() + { + throw new \Error('Trying to clone an uncloneable object of class Random\Engine\Secure'); + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php new file mode 100644 index 000000000..d216e0ade --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/AllowDynamicProperties.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80200) { + #[Attribute(Attribute::TARGET_CLASS)] + final class AllowDynamicProperties + { + public function __construct() + { + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php new file mode 100644 index 000000000..971ed570d --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/BrokenRandomEngineError.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +if (\PHP_VERSION_ID < 80200) { + class BrokenRandomEngineError extends RandomError + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php new file mode 100644 index 000000000..fb32496f1 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/CryptoSafeEngine.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +if (\PHP_VERSION_ID < 80200) { + interface CryptoSafeEngine extends Engine + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine.php new file mode 100644 index 000000000..4fc78c8fb --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +if (\PHP_VERSION_ID < 80200) { + interface Engine + { + public function generate(): string; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php new file mode 100644 index 000000000..e779b5445 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/Engine/Secure.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random\Engine; + +use Symfony\Polyfill\Php82 as p; + +if (\PHP_VERSION_ID < 80200) { + final class Secure extends p\Random\Engine\Secure implements \Random\CryptoSafeEngine + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php new file mode 100644 index 000000000..bf5e89e01 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomError.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +use Symfony\Polyfill\Php82\NoDynamicProperties; + +if (\PHP_VERSION_ID < 80200) { + class RandomError extends \Error + { + use NoDynamicProperties; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php new file mode 100644 index 000000000..3b9aae140 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/Random/RandomException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Random; + +use Symfony\Polyfill\Php82\NoDynamicProperties; + +if (\PHP_VERSION_ID < 80200) { + class RandomException extends \Exception + { + use NoDynamicProperties; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php new file mode 100644 index 000000000..aea4dfbdd --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameter.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80200) { + #[Attribute(Attribute::TARGET_PARAMETER)] + final class SensitiveParameter + { + public function __construct() + { + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php new file mode 100644 index 000000000..8349170b6 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/Resources/stubs/SensitiveParameterValue.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80200) { + final class SensitiveParameterValue extends Symfony\Polyfill\Php82\SensitiveParameterValue + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/SensitiveParameterValue.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/SensitiveParameterValue.php new file mode 100644 index 000000000..944c0a659 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/SensitiveParameterValue.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php82; + +/** + * @author Tim Düsterhus + * + * @internal + */ +class SensitiveParameterValue +{ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function getValue() + { + return $this->value; + } + + public function __debugInfo(): array + { + return []; + } + + public function __sleep(): array + { + throw new \Exception("Serialization of 'SensitiveParameterValue' is not allowed"); + } + + public function __wakeup(): void + { + throw new \Exception("Unserialization of 'SensitiveParameterValue' is not allowed"); + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php82/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-php82/bootstrap.php new file mode 100644 index 000000000..b8da6f083 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/bootstrap.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php82 as p; + +if (\PHP_VERSION_ID >= 80200) { + return; +} + +if (\extension_loaded('curl') && !defined('CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256') && curl_version()['version_number'] >= 0x075000) { + define('CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256', 10311); +} + +if (extension_loaded('odbc')) { + if (!function_exists('odbc_connection_string_is_quoted')) { + function odbc_connection_string_is_quoted(string $str): bool { return p\Php82::odbc_connection_string_is_quoted($str); } + } + + if (!function_exists('odbc_connection_string_should_quote')) { + function odbc_connection_string_should_quote(string $str): bool { return p\Php82::odbc_connection_string_should_quote($str); } + } + + if (!function_exists('odbc_connection_string_quote')) { + function odbc_connection_string_quote(string $str): string { return p\Php82::odbc_connection_string_quote($str); } + } +} + +if (!function_exists('ini_parse_quantity')) { + function ini_parse_quantity(string $shorthand): int { return p\Php82::ini_parse_quantity($shorthand); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-php82/composer.json similarity index 82% rename from data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json rename to data/web/inc/lib/vendor/symfony/polyfill-php82/composer.json index 28b6408ea..2031ae8d0 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/composer.json +++ b/data/web/inc/lib/vendor/symfony/polyfill-php82/composer.json @@ -1,7 +1,7 @@ { - "name": "symfony/polyfill-php81", + "name": "symfony/polyfill-php82", "type": "library", - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", "keywords": ["polyfill", "shim", "compatibility", "portable"], "homepage": "https://symfony.com", "license": "MIT", @@ -19,7 +19,7 @@ "php": ">=7.2" }, "autoload": { - "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, + "psr-4": { "Symfony\\Polyfill\\Php82\\": "" }, "files": [ "bootstrap.php" ], "classmap": [ "Resources/stubs" ] }, diff --git a/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-php83/LICENSE similarity index 85% rename from data/web/inc/lib/vendor/paragonie/random_compat/LICENSE rename to data/web/inc/lib/vendor/symfony/polyfill-php83/LICENSE index 45c7017df..733c826eb 100644 --- a/data/web/inc/lib/vendor/paragonie/random_compat/LICENSE +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/LICENSE @@ -1,13 +1,11 @@ -The MIT License (MIT) - -Copyright (c) 2015 Paragon Initiative Enterprises +Copyright (c) 2022-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. @@ -17,6 +15,5 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Php83.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Php83.php new file mode 100644 index 000000000..e3af829eb --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Php83.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php83; + +/** + * @author Ion Bazan + * @author Pierre Ambroise + * + * @internal + */ +final class Php83 +{ + private const JSON_MAX_DEPTH = 0x7FFFFFFF; // see https://www.php.net/manual/en/function.json-decode.php + + public static function json_validate(string $json, int $depth = 512, int $flags = 0): bool + { + if (0 !== $flags && \defined('JSON_INVALID_UTF8_IGNORE') && \JSON_INVALID_UTF8_IGNORE !== $flags) { + throw new \ValueError('json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)'); + } + + if ('' === $json) { + json_decode(''); + + return false; + } + + if ($depth <= 0) { + json_decode('0'); + throw new \ValueError('json_validate(): Argument #2 ($depth) must be greater than 0'); + } + + if ($depth > self::JSON_MAX_DEPTH) { + json_decode('0'); + throw new \ValueError(\sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH)); + } + + json_decode($json, true, $depth, $flags); + + return \JSON_ERROR_NONE === json_last_error(); + } + + /** @return string|false */ + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + $errorToTrigger = null; + try { + if (!@mb_check_encoding('', $encoding)) { + $errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding); + } + } catch (\ValueError $e) { + $errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding); + } + + if (null === $errorToTrigger && 0 >= $padStringLength = mb_strlen($pad_string, $encoding)) { + $errorToTrigger = 'mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'; + } + + if (null === $errorToTrigger && !\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + $errorToTrigger = 'mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'; + } + + if (null !== $errorToTrigger) { + if (80000 > \PHP_VERSION_ID) { + trigger_error($errorToTrigger, \E_USER_WARNING); + + return false; + } + + throw new \ValueError($errorToTrigger); + } + + $paddingRequired = $length - mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + $leftPaddingLength = $paddingRequired; + break; + case \STR_PAD_RIGHT: + $leftPaddingLength = 0; + break; + default: + $leftPaddingLength = intdiv($paddingRequired, 2); + } + + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return str_repeat($pad_string, intdiv($leftPaddingLength, $padStringLength)).mb_substr($pad_string, 0, $leftPaddingLength % $padStringLength, $encoding) + .$string + .str_repeat($pad_string, intdiv($rightPaddingLength, $padStringLength)).mb_substr($pad_string, 0, $rightPaddingLength % $padStringLength, $encoding); + } + + public static function str_increment(string $string): string + { + if ('' === $string) { + throw new \ValueError('str_increment(): Argument #1 ($string) cannot be empty'); + } + + if (!preg_match('/^[a-zA-Z0-9]++$/D', $string)) { + throw new \ValueError('str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters'); + } + + for ($i = \strlen($string) - 1; $i >= 0; --$i) { + $char = $string[$i]; + + if ('z' === $char) { + $string[$i] = 'a'; + continue; + } + if ('Z' === $char) { + $string[$i] = 'A'; + continue; + } + if ('9' === $char) { + $string[$i] = '0'; + continue; + } + + $string[$i] = \chr(\ord($char) + 1); + + return $string; + } + + switch ($string[0]) { + case 'a': return 'a'.$string; + case 'A': return 'A'.$string; + } + + return '1'.$string; + } + + public static function str_decrement(string $string): string + { + if ('' === $string) { + throw new \ValueError('str_decrement(): Argument #1 ($string) cannot be empty'); + } + + if (!preg_match('/^[a-zA-Z0-9]++$/D', $string)) { + throw new \ValueError('str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters'); + } + + if ('0' === $string[0] || 'a' === $string || 'A' === $string) { + throw new \ValueError(\sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string)); + } + + for ($i = \strlen($string) - 1; $i >= 0; --$i) { + $char = $string[$i]; + + if ('a' === $char) { + $string[$i] = 'z'; + continue; + } + if ('A' === $char) { + $string[$i] = 'Z'; + continue; + } + if ('0' === $char) { + $string[$i] = '9'; + continue; + } + + $string[$i] = \chr(\ord($char) - 1); + + return '0' === $string[0] && isset($string[1]) ? substr($string, 1) : $string; + } + + return substr($string, 1); + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/README.md b/data/web/inc/lib/vendor/symfony/polyfill-php83/README.md new file mode 100644 index 000000000..f29877681 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/README.md @@ -0,0 +1,22 @@ +Symfony Polyfill / Php83 +======================== + +This component provides features added to PHP 8.3 core: + +- [`json_validate`](https://wiki.php.net/rfc/json_validate) +- [`Override`](https://wiki.php.net/rfc/marking_overriden_methods) +- [`mb_str_pad`](https://wiki.php.net/rfc/mb_str_pad) +- [`ldap_exop_sync`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures) +- [`ldap_connect_wallet`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures) +- [`stream_context_set_options`](https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures) +- [`str_increment` and `str_decrement`](https://wiki.php.net/rfc/saner-inc-dec-operators) +- [`Date*Exception/Error classes`](https://wiki.php.net/rfc/datetime-exceptions) +- [`SQLite3Exception`](https://wiki.php.net/rfc/sqlite3_exceptions) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php new file mode 100644 index 000000000..6e7ed8c86 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateError extends Error + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php new file mode 100644 index 000000000..041710af5 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateException extends Exception + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php new file mode 100644 index 000000000..e2e9dfc95 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateInvalidOperationException extends DateException + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php new file mode 100644 index 000000000..75bcd267a --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateInvalidTimeZoneException extends DateException + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php new file mode 100644 index 000000000..af91b8e4d --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateMalformedIntervalStringException extends DateException + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php new file mode 100644 index 000000000..9b6d2764e --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateMalformedPeriodStringException extends DateException + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php new file mode 100644 index 000000000..7ad04849f --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateMalformedStringException extends DateException + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php new file mode 100644 index 000000000..11f0edc68 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateObjectError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateObjectError extends DateError + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php new file mode 100644 index 000000000..98e67036e --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/DateRangeError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class DateRangeError extends DateError + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/Override.php similarity index 83% rename from data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php rename to data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/Override.php index cb7720a8d..d3e6b3e1d 100644 --- a/data/web/inc/lib/vendor/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/Override.php @@ -9,9 +9,9 @@ * file that was distributed with this source code. */ -if (\PHP_VERSION_ID < 80100) { +if (\PHP_VERSION_ID < 80300) { #[Attribute(Attribute::TARGET_METHOD)] - final class ReturnTypeWillChange + final class Override { public function __construct() { diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php new file mode 100644 index 000000000..ecb7c98e0 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80300) { + class SQLite3Exception extends Exception + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap.php new file mode 100644 index 000000000..95cf7830d --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php83 as p; + +if (\PHP_VERSION_ID >= 80300) { + return; +} + +if (!function_exists('json_validate')) { + function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); } +} + +if (!function_exists('stream_context_set_options')) { + function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); } +} + +if (!function_exists('str_increment')) { + function str_increment(string $string): string { return p\Php83::str_increment($string); } +} + +if (!function_exists('str_decrement')) { + function str_decrement(string $string): string { return p\Php83::str_decrement($string); } +} + +if (\PHP_VERSION_ID < 80000) { + require __DIR__.'/bootstrap72.php'; +} + +if (extension_loaded('mbstring')) { + if (!function_exists('mb_str_pad')) { + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } + } +} + +if (\PHP_VERSION_ID >= 80100) { + return require __DIR__.'/bootstrap81.php'; +} + +if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) { + function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $response_data, $response_oid); } +} + +if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) { + function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap72.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap72.php new file mode 100644 index 000000000..3da5e401c --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap72.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php83 as p; + +if (!class_exists('ValueError', false)) { + class ValueError extends Error + { + } +} + +if (extension_loaded('mbstring')) { + if (!function_exists('mb_str_pad')) { + /** @return string|false */ + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Php83::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap81.php b/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap81.php new file mode 100644 index 000000000..4c2581134 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/bootstrap81.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php83 as p; + +if (\PHP_VERSION_ID >= 80300) { + return; +} + +if (!function_exists('json_validate')) { + function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); } +} + +if (!function_exists('str_increment')) { + function str_increment(string $string): string { return p\Php83::str_increment($string); } +} + +if (!function_exists('str_decrement')) { + function str_decrement(string $string): string { return p\Php83::str_decrement($string); } +} + +if (extension_loaded('mbstring') && !function_exists('mb_str_pad')) { + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } +} + +if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) { + function ldap_exop_sync(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $response_data, $response_oid); } +} + +if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) { + function ldap_connect_wallet(?string $uri, string $wallet, #[\SensitiveParameter] string $password, int $auth_mode = \GSLC_SSL_NO_AUTH): \LDAP\Connection|false { return ldap_connect($uri, $wallet, $password, $auth_mode); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php83/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-php83/composer.json new file mode 100644 index 000000000..a8b8ba707 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php83/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php83", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php83\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-php84/LICENSE new file mode 100644 index 000000000..e374a5c83 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Php84.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Php84.php new file mode 100644 index 000000000..818c3d8b8 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Php84.php @@ -0,0 +1,549 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php84; + +/** + * @author Ayesh Karunaratne + * @author Pierre Ambroise + * + * @internal + */ +final class Php84 +{ + private const BCMATH_WELL_FORMED = '/^[+-]?[0-9]*+(?:\.[0-9]*+)?$/D'; + + /** @return string|false */ + public static function mb_ucfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + try { + $validEncoding = @mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + if (!$validEncoding) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + /** @return string|false */ + public static function mb_lcfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + try { + $validEncoding = @mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + if (!$validEncoding) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + public static function array_find(array $array, callable $callback) + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return null; + } + + public static function array_find_key(array $array, callable $callback) + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $key; + } + } + + return null; + } + + public static function array_any(array $array, callable $callback): bool + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return true; + } + } + + return false; + } + + public static function array_all(array $array, callable $callback): bool + { + foreach ($array as $key => $value) { + if (!$callback($value, $key)) { + return false; + } + } + + return true; + } + + public static function fpow(float $num, float $exponent): float + { + if (0.0 === $num && $exponent < 0.0) { + // IEEE 754 pow(±0, y < 0), which the ** operator deprecates since PHP 8.4 + return -1.0 === fmod($exponent, 2.0) && '-' === ((string) $num)[0] ? -\INF : \INF; + } + + return $num ** $exponent; + } + + /** @return string|false */ + public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) + { + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + try { + $validEncoding = @mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding)); + } + + if (!$validEncoding) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding), \E_USER_WARNING); + + return false; + } + + throw new \ValueError(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding)); + } + + if ('' === $characters) { + return $string; + } + + if ('UTF-8' === $encoding || \in_array(strtolower($encoding), ['utf-8', 'utf8'], true)) { + $encoding = 'UTF-8'; + } + + if ('UTF-8' === $encoding && (!preg_match('//u', $string) || (null !== $characters && !preg_match('//u', $characters)))) { + return self::mb_trim_invalid_utf8($string, $characters, $function); + } + + $string = mb_convert_encoding($string, 'UTF-8', $encoding); + + if (null !== $characters) { + $characters = mb_convert_encoding($characters, 'UTF-8', $encoding); + } + + if (null === $characters) { + $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; + } else { + $characters = preg_quote($characters); + } + + $string = preg_replace(\sprintf($regex, $characters), '', $string); + + if ('UTF-8' === $encoding) { + return $string; + } + + return mb_convert_encoding($string, $encoding, 'UTF-8'); + } + + /** + * Trims ill-formed UTF-8 like mbstring: each maximal subpart of an ill-formed + * sequence counts as one character, and the string is returned as is when + * nothing is trimmed, else re-encoded with the substitute character. + */ + private static function mb_trim_invalid_utf8(string $string, ?string $characters, string $function): string + { + // Well-formed characters in group 1, then the maximal subparts of ill-formed sequences + $regex = '/([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})|\xE0[\xA0-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]|\xED[\x80-\x9F]|\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|[\x80-\xFF]/'; + + // Ill-formed sequences leave group 1 empty: they are trimmed when $characters has one too + preg_match_all($regex, $characters ?? "\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", $m); + $trimmed = array_flip($m[1]); + + $start = 0; + $end = \strlen($string); + + while ('mb_rtrim' !== $function && $start < $end && preg_match($regex, $string, $m, 0, $start) && isset($trimmed[$m[1] ?? ''])) { + $start += \strlen($m[0]); + } + + while ('mb_ltrim' !== $function && $start < $end) { + // Characters are at most 4 bytes long, and only continuation bytes don't start one + for ($i = $end - 1; $i > $start && $i > $end - 4 && 0x80 === (\ord($string[$i]) & 0xC0); --$i) { + } + + preg_match_all($regex, substr($string, $i, $end - $i), $m); + + if (!isset($trimmed[end($m[1])])) { + break; + } + + $end -= \strlen(end($m[0])); + } + + if (0 === $start && \strlen($string) === $end) { + return $string; + } + + $string = substr($string, $start, $end - $start); + + return 'none' === mb_substitute_character() ? preg_replace($regex, '$1', $string) : mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + } + + public static function grapheme_str_split(string $string, int $length) + { + if (0 >= $length || 1073741823 < $length) { + throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823'); + } + + if ('' === $string) { + return []; + } + + $regex = ((float) \PCRE_VERSION >= 10.44) + ? '\X' + : '(?:\r\n|[\x{1F1E6}-\x{1F1FF}][\x{1F1E6}-\x{1F1FF}]?|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*(?:\x{200D}(?:[\x{1F1E6}-\x{1F1FF}]|[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가-힣]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*)*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + + if (preg_match_all('/'.$regex.'/u', $string, $matches)) { + $graphemes = $matches[0]; + } else { + $graphemes = self::splitIllFormed($string, $regex); + } + + if (!$graphemes) { + return false; + } + + if (1 === $length) { + return $graphemes; + } + + $chunks = array_chunk($graphemes, $length); + foreach ($chunks as &$chunk) { + $chunk = implode('', $chunk); + } + + return $chunks; + } + + public static function bcceil(string $num): string + { + if (!preg_match(self::BCMATH_WELL_FORMED, $num)) { + throw new \ValueError('bcceil(): Argument #1 ($num) is not well-formed'); + } + + return self::bcround($num, 0, \RoundingMode::PositiveInfinity); + } + + public static function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array + { + if (null === $quot = @bcdiv($num1, $num2, 0)) { + throw new \DivisionByZeroError('Division by zero'); + } + $scale = $scale ?? (\PHP_VERSION_ID >= 70300 ? bcscale() : (\ini_get('bcmath.scale') ?: 0)); + + return [$quot, bcmod($num1, $num2, $scale)]; + } + + public static function bcfloor(string $num): string + { + if (!preg_match(self::BCMATH_WELL_FORMED, $num)) { + throw new \ValueError('bcfloor(): Argument #1 ($num) is not well-formed'); + } + + return self::bcround($num, 0, \RoundingMode::NegativeInfinity); + } + + /** + * @param \RoundingMode|\RoundingMode::* $mode + */ + public static function bcround(string $num, int $precision = 0, $mode = \RoundingMode::HalfAwayFromZero): string + { + if (!preg_match(self::BCMATH_WELL_FORMED, $num)) { + throw new \ValueError('bcround(): Argument #1 ($num) is not well-formed'); + } + + $sign = 1; + if ('' !== $num && ('-' === $num[0] || '+' === $num[0])) { + if ('-' === $num[0]) { + $sign = -1; + } + + $num = substr($num, 1); + } + + if (false !== strpos($num, '.')) { + [$intPart, $fracPart] = array_pad(explode('.', $num, 2), 2, ''); + } else { + $intPart = $num; + $fracPart = ''; + } + + if ('' === $intPart) { + $intPart = '0'; + } + + $intPart = self::trimLeadingZeros($intPart); + $fracPart = (string) $fracPart; + + if ($precision >= 0) { + $fracLength = \strlen($fracPart); + + if ($precision <= $fracLength) { + $scaledInt = $intPart.(string) substr($fracPart, 0, $precision); + $scaledFrac = (string) substr($fracPart, $precision); + } else { + $scaledInt = $intPart.$fracPart.str_repeat('0', $precision - $fracLength); + $scaledFrac = ''; + } + } else { + $shift = -$precision; + $intLength = \strlen($intPart); + + if ($shift <= $intLength) { + $splitPos = $intLength - $shift; + $scaledInt = substr($intPart, 0, $splitPos); + $scaledInt = '' === $scaledInt ? '0' : $scaledInt; + $scaledFrac = substr($intPart, $splitPos).$fracPart; + } else { + $scaledInt = '0'; + $scaledFrac = str_repeat('0', $shift - $intLength).$intPart.$fracPart; + } + } + + $roundedInt = self::roundIntegerPart($scaledInt, $scaledFrac, $sign, $mode); + $isZero = '' === trim($roundedInt, '0'); + $absResult = self::formatRoundedDigits($roundedInt, $precision); + + if (-1 === $sign && !$isZero) { + $absResult = '-'.$absResult; + } + + return $absResult; + } + + private static function roundIntegerPart(string $intPart, string $fracPart, int $sign, $mode): string + { + $intPart = self::trimLeadingZeros($intPart); + + if ('' === $fracPart || '' === trim($fracPart, '0')) { + return $intPart; + } + + $firstDigit = $fracPart[0]; + $tail = (string) substr($fracPart, 1); + $tailNonZero = '' !== trim($tail, '0'); + $isGreaterThanHalf = $firstDigit > '5' || ('5' === $firstDigit && $tailNonZero); + $isExactlyHalf = '5' === $firstDigit && !$tailNonZero; + $shouldIncrease = false; + + switch ($mode) { + case \RoundingMode::TowardsZero: + break; + + case \RoundingMode::AwayFromZero: + $shouldIncrease = true; + break; + + case \RoundingMode::PositiveInfinity: + $shouldIncrease = $sign > 0; + break; + + case \RoundingMode::NegativeInfinity: + $shouldIncrease = $sign < 0; + break; + + case \RoundingMode::HalfAwayFromZero: + $shouldIncrease = $isGreaterThanHalf || $isExactlyHalf; + break; + + case \RoundingMode::HalfTowardsZero: + $shouldIncrease = $isGreaterThanHalf; + break; + + case \RoundingMode::HalfEven: + if ($isGreaterThanHalf) { + $shouldIncrease = true; + } elseif ($isExactlyHalf && 1 === self::lastDigit($intPart) % 2) { + $shouldIncrease = true; + } + break; + + case \RoundingMode::HalfOdd: + if ($isGreaterThanHalf) { + $shouldIncrease = true; + } elseif ($isExactlyHalf && 0 === self::lastDigit($intPart) % 2) { + $shouldIncrease = true; + } + break; + } + + if ($shouldIncrease) { + $intPart = self::incrementDigits($intPart); + } + + return self::trimLeadingZeros($intPart); + } + + private static function formatRoundedDigits(string $roundedInt, int $precision): string + { + if ($precision > 0) { + if (\strlen($roundedInt) <= $precision) { + $roundedInt = str_pad($roundedInt, $precision + 1, '0', \STR_PAD_LEFT); + } + + $intDigits = substr($roundedInt, 0, -$precision); + $fracDigits = substr($roundedInt, -$precision); + + $intDigits = self::trimLeadingZeros('' === $intDigits ? '0' : $intDigits); + $fracDigits = str_pad($fracDigits, $precision, '0', \STR_PAD_LEFT); + + return $intDigits.'.'.$fracDigits; + } + + if (0 === $precision) { + return self::trimLeadingZeros($roundedInt); + } + + $shift = -$precision; + $digits = $roundedInt.str_repeat('0', $shift); + + return self::trimLeadingZeros($digits); + } + + private static function incrementDigits(string $digits): string + { + $digits = '' === $digits ? '0' : $digits; + $index = \strlen($digits) - 1; + $result = $digits; + $carry = 1; + + while ($index >= 0 && $carry) { + $value = \ord($result[$index]) - 48 + $carry; + $carry = $value >= 10 ? 1 : 0; + $result[$index] = \chr(48 + ($value % 10)); + --$index; + } + + return $carry ? '1'.$result : $result; + } + + private static function trimLeadingZeros(string $digits): string + { + $digits = ltrim($digits, '0'); + + return '' === $digits ? '0' : $digits; + } + + private static function lastDigit(string $digits): int + { + $length = \strlen($digits); + + return $length ? \ord($digits[$length - 1]) - 48 : 0; + } + + /** + * Splits a string that is not valid UTF-8 into grapheme clusters. + * + * The intl extension keeps the ill-formed bytes and breaks around them the + * way it would around the U+FFFD each maximal subpart stands for. Standing + * one in for real makes the cluster regexp applicable, then each cluster is + * mapped back to the bytes it was made of. + */ + private static function splitIllFormed(string $string, string $regex): array + { + // Table 3-7 of the Unicode standard for the well-formed byte sequences (group 1), + // then the maximal subparts an ill-formed sequence is made of (group 2). + preg_match_all('/([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|[\xEE-\xEF][\x80-\xBF]{2}|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})|(\xF0[\x90-\xBF][\x80-\xBF]?|[\xF1-\xF3][\x80-\xBF]{1,2}|\xF4[\x80-\x8F][\x80-\xBF]?|\xE0[\xA0-\xBF]|[\xE1-\xEC][\x80-\xBF]|\xED[\x80-\x9F]|[\xEE-\xEF][\x80-\xBF]|[\x80-\xFF])/s', $string, $matches, \PREG_SET_ORDER); + + $segments = []; + $segmentAt = []; + $sanitized = ''; + + foreach ($matches as $m) { + $segmentAt[\strlen($sanitized)] = \count($segments); + + if (isset($m[2]) && '' !== $m[2]) { + $segments[] = $m[2]; + $sanitized .= "\xEF\xBF\xBD"; + } else { + $segments[] = $m[1]; + $sanitized .= $m[1]; + } + } + + $segmentAt[\strlen($sanitized)] = \count($segments); + + preg_match_all('/'.$regex.'/u', $sanitized, $clusters, \PREG_OFFSET_CAPTURE); + + $graphemes = []; + + foreach ($clusters[0] as $cluster) { + $start = $segmentAt[$cluster[1]]; + $graphemes[] = implode('', \array_slice($segments, $start, $segmentAt[$cluster[1] + \strlen($cluster[0])] - $start)); + } + + return $graphemes; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/README.md b/data/web/inc/lib/vendor/symfony/polyfill-php84/README.md new file mode 100644 index 000000000..d1e335dc3 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/README.md @@ -0,0 +1,23 @@ +Symfony Polyfill / Php84 +======================== + +This component provides features added to PHP 8.4 core: + +- [`array_find`, `array_find_key`, `array_any` and `array_all`](https://wiki.php.net/rfc/array_find) +- [`bcdivmod`](https://wiki.php.net/rfc/add_bcdivmod_to_bcmath) +- [`Deprecated`](https://wiki.php.net/rfc/deprecated_attribute) +- `CURL_HTTP_VERSION_3` and `CURL_HTTP_VERSION_3ONLY` constants +- [`fpow`](https://wiki.php.net/rfc/raising_zero_to_power_of_negative_number) +- [`grapheme_str_split`](https://wiki.php.net/rfc/grapheme_str_split) +- [`mb_trim`, `mb_ltrim` and `mb_rtrim`](https://wiki.php.net/rfc/mb_trim) +- [`mb_ucfirst` and `mb_lcfirst`](https://wiki.php.net/rfc/mb_ucfirst) +- [`PDO` driver specific sub-classes](https://wiki.php.net/rfc/pdo_driver_specific_subclasses) +- [`ReflectionConstant`](https://github.com/php/php-src/pull/13669) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/Deprecated.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/Deprecated.php new file mode 100644 index 000000000..f3e6a4f8e --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/Deprecated.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80400) { + #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::TARGET_CLASS_CONSTANT)] + final class Deprecated + { + public readonly ?string $message; + public readonly ?string $since; + + public function __construct(?string $message = null, ?string $since = null) + { + $this->message = $message; + $this->since = $since; + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/RoundingMode.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/RoundingMode.php new file mode 100644 index 000000000..a267309ce --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/RoundingMode.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80400) { + enum RoundingMode + { + case HalfAwayFromZero; + case HalfTowardsZero; + case HalfEven; + case HalfOdd; + case TowardsZero; + case AwayFromZero; + case NegativeInfinity; + case PositiveInfinity; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.php new file mode 100644 index 000000000..cbc731ec1 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80100) { + #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::TARGET_CLASS_CONSTANT)] + final class Deprecated + { + /** + * @readonly + */ + public ?string $message; + + /** + * @readonly + */ + public ?string $since; + + public function __construct(?string $message = null, ?string $since = null) + { + $this->message = $message; + $this->since = $since; + } + } +} elseif (\PHP_VERSION_ID < 80400) { + require dirname(__DIR__).'/Deprecated.php'; +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php new file mode 100644 index 000000000..06080b06b --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Pdo; + +if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_dblib')) { + class Dblib extends \PDO + { + public const ATTR_CONNECTION_TIMEOUT = \PDO::DBLIB_ATTR_CONNECTION_TIMEOUT; + public const ATTR_QUERY_TIMEOUT = \PDO::DBLIB_ATTR_QUERY_TIMEOUT; + public const ATTR_STRINGIFY_UNIQUEIDENTIFIER = \PDO::DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER; + public const ATTR_VERSION = \PDO::DBLIB_ATTR_VERSION; + public const ATTR_TDS_VERSION = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_TDS_VERSION : 1004; + public const ATTR_SKIP_EMPTY_ROWSETS = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_SKIP_EMPTY_ROWSETS : 1005; + public const ATTR_DATETIME_CONVERT = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_DATETIME_CONVERT : 1006; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('dblib' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Dblib::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Dblib::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Dblib::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php new file mode 100644 index 000000000..7dbb62896 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Pdo; + +if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_firebird')) { + class Firebird extends \PDO + { + public const ATTR_DATE_FORMAT = \PDO::FB_ATTR_DATE_FORMAT; + public const ATTR_TIME_FORMAT = \PDO::FB_ATTR_TIME_FORMAT; + public const ATTR_TIMESTAMP_FORMAT = \PDO::FB_ATTR_TIMESTAMP_FORMAT; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('firebird' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Firebird::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Firebird::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Firebird::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php new file mode 100644 index 000000000..74c3abdee --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Pdo; + +use PDO; + +if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_mysql')) { + // Feature detection for non-mysqlnd; see also https://www.php.net/manual/en/class.pdo-mysql.php#pdo-mysql.constants.attr-max-buffer-size + if (\defined('PDO::MYSQL_ATTR_MAX_BUFFER_SIZE') && \defined('PDO::MYSQL_ATTR_READ_DEFAULT_FILE') && \defined('PDO::MYSQL_ATTR_READ_DEFAULT_GROUP')) { + class Mysql extends \PDO + { + public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS; + public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY; + public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS; + public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE; + public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND; + public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE; + public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015; + public const ATTR_MAX_BUFFER_SIZE = \PDO::MYSQL_ATTR_MAX_BUFFER_SIZE; + public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS; + public const ATTR_READ_DEFAULT_FILE = \PDO::MYSQL_ATTR_READ_DEFAULT_FILE; + public const ATTR_READ_DEFAULT_GROUP = \PDO::MYSQL_ATTR_READ_DEFAULT_GROUP; + public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY; + public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; + public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH; + public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; + public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER; + public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; + public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + } + } elseif (\defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) { + class Mysql extends \PDO + { + public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS; + public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY; + public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS; + public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE; + public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND; + public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE; + public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015; + // public const ATTR_MAX_BUFFER_SIZE = PDO::MYSQL_ATTR_MAX_BUFFER_SIZE; // disabled for mysqlnd + public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS; + // public const ATTR_READ_DEFAULT_FILE = PDO::MYSQL_ATTR_READ_DEFAULT_FILE; // disabled for mysqlnd + // public const ATTR_READ_DEFAULT_GROUP = PDO::MYSQL_ATTR_READ_DEFAULT_GROUP; // disabled for mysqlnd + public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY; + public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; + public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH; + public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; + public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER; + public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; + public const ATTR_SSL_VERIFY_SERVER_CERT = \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT; + public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + } + } else { + class Mysql extends \PDO + { + public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS; + public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY; + public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS; + public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE; + public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND; + public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE; + public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015; + // public const ATTR_MAX_BUFFER_SIZE = PDO::MYSQL_ATTR_MAX_BUFFER_SIZE; // disabled for mysqlnd + public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS; + // public const ATTR_READ_DEFAULT_FILE = PDO::MYSQL_ATTR_READ_DEFAULT_FILE; // disabled for mysqlnd + // public const ATTR_READ_DEFAULT_GROUP = PDO::MYSQL_ATTR_READ_DEFAULT_GROUP; // disabled for mysqlnd + public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY; + public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA; + public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH; + public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT; + public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER; + public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY; + public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php new file mode 100644 index 000000000..19e8e62b5 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Pdo; + +if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_odbc')) { + class Odbc extends \PDO + { + public const ATTR_USE_CURSOR_LIBRARY = \PDO::ODBC_ATTR_USE_CURSOR_LIBRARY; + public const ATTR_ASSUME_UTF8 = \PDO::ODBC_ATTR_ASSUME_UTF8; + public const SQL_USE_IF_NEEDED = \PDO::ODBC_SQL_USE_IF_NEEDED; + public const SQL_USE_DRIVER = \PDO::ODBC_SQL_USE_DRIVER; + public const SQL_USE_ODBC = \PDO::ODBC_SQL_USE_ODBC; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('odbc' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Odbc::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Odbc::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Odbc::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php new file mode 100644 index 000000000..98ea1e324 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Pdo; + +if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_pgsql')) { + class Pgsql extends \PDO + { + public const ATTR_DISABLE_PREPARES = \PDO::PGSQL_ATTR_DISABLE_PREPARES; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('pgsql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Pgsql::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Pgsql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Pgsql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + + public function copyFromArray(string $tableName, array $rows, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool + { + return $this->pgsqlCopyFromArray($tableName, $rows, $separator, $nullAs, $fields); + } + + public function copyFromFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool + { + return $this->pgsqlCopyFromFile($tableName, $filename, $separator, $nullAs, $fields); + } + + /** + * @return array|false + */ + public function copyToArray(string $tableName, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null) + { + return $this->pgsqlCopyToArray($tableName, $separator, $nullAs, $fields); + } + + public function copyToFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool + { + return $this->pgsqlCopyToFile($tableName, $filename, $separator, $nullAs, $fields); + } + + /** + * @return array|false + */ + public function getNotify(int $fetchMode = \PDO::FETCH_DEFAULT, int $timeoutMilliseconds = 0) + { + return $this->pgsqlGetNotify($fetchMode, $timeoutMilliseconds); + } + + public function getPid(): int + { + return $this->pgsqlGetPid(); + } + + /** + * @return string|false + */ + public function lobCreate() + { + return $this->pgsqlLOBCreate(); + } + + /** + * @return resource|false + */ + public function lobOpen(string $oid, string $mode = 'rb') + { + return $this->pgsqlLOBOpen($oid, $mode); + } + + public function lobUnlink(string $oid): bool + { + return $this->pgsqlLOBUnlink($oid); + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php new file mode 100644 index 000000000..69c47ce96 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Pdo; + +if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_sqlite')) { + class Sqlite extends \PDO + { + public const ATTR_EXTENDED_RESULT_CODES = \PHP_VERSION_ID >= 70400 ? \PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES : 1002; + public const ATTR_OPEN_FLAGS = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_ATTR_OPEN_FLAGS : 1000; + public const ATTR_READONLY_STATEMENT = \PHP_VERSION_ID >= 70400 ? \PDO::SQLITE_ATTR_READONLY_STATEMENT : 1001; + public const DETERMINISTIC = \PDO::SQLITE_DETERMINISTIC; + public const OPEN_READONLY = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_READONLY : 1; + public const OPEN_READWRITE = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_READWRITE : 2; + public const OPEN_CREATE = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_CREATE : 4; + + public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null) + { + parent::__construct($dsn, $username, $password, $options); + + if ('sqlite' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + throw new \PDOException(\sprintf('Pdo\Sqlite::__construct() cannot be used for connecting to the "%s" driver', $driver)); + } + } + + public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self + { + try { + return new self($dsn, $username, $password, $options); + } catch (\PDOException $e) { + throw preg_match('/^Pdo\\\\Sqlite::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Sqlite::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e; + } + } + + public function createAggregate(string $name, callable $step, callable $finalize, int $numArgs = -1): bool + { + return $this->sqliteCreateAggregate($name, $step, $finalize, $numArgs); + } + + public function createCollation(string $name, callable $callback): bool + { + return $this->sqliteCreateCollation($name, $callback); + } + + public function createFunction(string $function_name, callable $callback, int $num_args = -1, int $flags = 0): bool + { + return $this->sqliteCreateFunction($function_name, $callback, $num_args, $flags); + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php new file mode 100644 index 000000000..5ab3bf377 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80400) { + // @author Daniel Scherzer + final class ReflectionConstant + { + /** + * @var string + * + * @readonly + */ + public $name; + + private $value; + private $deprecated; + private $persistent; + + private static $persistentConstants = []; + + public function __construct(string $name) + { + if (!defined($name) || false !== strpos($name, '::')) { + throw new ReflectionException("Constant \"$name\" does not exist"); + } + + $this->name = ltrim($name, '\\'); + $deprecated = false; + $eh = set_error_handler(static function ($type, $msg, $file, $line) use ($name, &$deprecated, &$eh) { + if (\E_DEPRECATED === $type && "Constant $name is deprecated" === $msg) { + return $deprecated = true; + } + + return $eh && $eh($type, $msg, $file, $line); + }); + + try { + $this->value = constant($name); + $this->deprecated = $deprecated; + } finally { + restore_error_handler(); + } + } + + public function getName(): string + { + return $this->name; + } + + public function getValue() + { + return $this->value; + } + + public function getNamespaceName(): string + { + if (false === $slashPos = strrpos($this->name, '\\')) { + return ''; + } + + return substr($this->name, 0, $slashPos); + } + + public function getShortName(): string + { + if (false === $slashPos = strrpos($this->name, '\\')) { + return $this->name; + } + + return substr($this->name, $slashPos + 1); + } + + public function isDeprecated(): bool + { + return $this->deprecated; + } + + public function __toString(): string + { + // A constant is persistent if provided by PHP itself rather than + // being defined by users. If we got here, we know that it *is* + // defined, so we just need to figure out if it is defined by the + // user or not + if (!self::$persistentConstants) { + $persistentConstants = get_defined_constants(true); + unset($persistentConstants['user']); + foreach ($persistentConstants as $constants) { + self::$persistentConstants += $constants; + } + } + $persistent = array_key_exists($this->name, self::$persistentConstants); + + // Can't match the inclusion of `no_file_cache` but the rest is + // possible to match + $result = 'Constant [ '; + if ($persistent || $this->deprecated) { + $result .= '<'; + if ($persistent) { + $result .= 'persistent'; + if ($this->deprecated) { + $result .= ', '; + } + } + if ($this->deprecated) { + $result .= 'deprecated'; + } + $result .= '> '; + } + // Cannot just use gettype() to match zend_zval_type_name() + if (is_object($this->value)) { + $result .= \PHP_VERSION_ID >= 80000 ? get_debug_type($this->value) : gettype($this->value); + } elseif (is_bool($this->value)) { + $result .= 'bool'; + } elseif (is_int($this->value)) { + $result .= 'int'; + } elseif (is_float($this->value)) { + $result .= 'float'; + } elseif (null === $this->value) { + $result .= 'null'; + } else { + $result .= gettype($this->value); + } + $result .= ' '; + $result .= $this->name; + $result .= ' ] { '; + if (is_array($this->value)) { + $result .= 'Array'; + } else { + // This will throw an exception if the value is an object that + // cannot be converted to string; that is expected and matches + // the behavior of zval_get_string_func() + $result .= (string) $this->value; + } + $result .= " }\n"; + + return $result; + } + + public function __sleep(): array + { + throw new Exception("Serialization of 'ReflectionConstant' is not allowed"); + } + + public function __wakeup(): void + { + throw new Exception("Unserialization of 'ReflectionConstant' is not allowed"); + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/RoundingMode.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/RoundingMode.php new file mode 100644 index 000000000..b7f29f174 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/Resources/stubs/RoundingMode.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80100) { + final class RoundingMode + { + const HalfAwayFromZero = 0; + const HalfTowardsZero = 1; + const HalfEven = 2; + const HalfOdd = 3; + const TowardsZero = 4; + const AwayFromZero = 5; + const NegativeInfinity = 6; + const PositiveInfinity = 7; + + private function __construct() + { + } + + public static function cases(): array + { + return [ + self::HalfAwayFromZero, + self::HalfTowardsZero, + self::HalfEven, + self::HalfOdd, + self::TowardsZero, + self::AwayFromZero, + self::NegativeInfinity, + self::PositiveInfinity, + ]; + } + } +} elseif (\PHP_VERSION_ID < 80400) { + require dirname(__DIR__).'/RoundingMode.php'; +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap.php new file mode 100644 index 000000000..1854b8f82 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php84 as p; + +if (\PHP_VERSION_ID >= 80400) { + return; +} + +if (\extension_loaded('curl')) { + // CURL_VERSION_HTTP3 is defined by PHP 8.2+ when libcurl >= 7.66.0 + if (defined('CURL_VERSION_HTTP3') || \PHP_VERSION_ID < 80200 && curl_version()['version_number'] >= 0x074200) { + if (!defined('CURL_HTTP_VERSION_3')) { + define('CURL_HTTP_VERSION_3', 30); + } + + // CURL_HTTP_VERSION_3ONLY requires libcurl >= 7.88.0 and is not gated by any PHP-defined constant before 8.4 + if (!defined('CURL_HTTP_VERSION_3ONLY') && curl_version()['version_number'] >= 0x075800) { + define('CURL_HTTP_VERSION_3ONLY', 31); + } + } +} + +if (!function_exists('array_find')) { + function array_find(array $array, callable $callback) { return p\Php84::array_find($array, $callback); } +} + +if (!function_exists('array_find_key')) { + function array_find_key(array $array, callable $callback) { return p\Php84::array_find_key($array, $callback); } +} + +if (!function_exists('array_any')) { + function array_any(array $array, callable $callback): bool { return p\Php84::array_any($array, $callback); } +} + +if (!function_exists('array_all')) { + function array_all(array $array, callable $callback): bool { return p\Php84::array_all($array, $callback); } +} + +if (!function_exists('fpow')) { + function fpow(float $num, float $exponent): float { return p\Php84::fpow($num, $exponent); } +} + +if (\PHP_VERSION_ID < 80000) { + require __DIR__.'/bootstrap72.php'; +} + +if (extension_loaded('mbstring')) { + if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(?string $string, ?string $encoding = null): string { return p\Php84::mb_ucfirst((string) $string, $encoding); } + } + + if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(?string $string, ?string $encoding = null): string { return p\Php84::mb_lcfirst((string) $string, $encoding); } + } + + if (!function_exists('mb_trim')) { + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_trim((string) $string, $characters, $encoding); } + } + + if (!function_exists('mb_ltrim')) { + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_ltrim((string) $string, $characters, $encoding); } + } + + if (!function_exists('mb_rtrim')) { + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_rtrim((string) $string, $characters, $encoding); } + } +} + +if (extension_loaded('bcmath')) { + if (!function_exists('bcceil')) { + function bcceil(string $num): string { return p\Php84::bcceil($num); } + } + if (!function_exists('bcdivmod')) { + function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array { return p\Php84::bcdivmod($num1, $num2, $scale); } + } + if (!function_exists('bcfloor')) { + function bcfloor(string $num): string { return p\Php84::bcfloor($num); } + } + if (!function_exists('bcround')) { + function bcround(string $num, int $precision = 0, $mode = \RoundingMode::HalfAwayFromZero): string { return p\Php84::bcround($num, $precision, $mode); } + } +} + +if (\PHP_VERSION_ID >= 80200) { + return require __DIR__.'/bootstrap82.php'; +} + +if (extension_loaded('intl') && !function_exists('grapheme_str_split')) { + function grapheme_str_split(string $string, int $length = 1) { return p\Php84::grapheme_str_split($string, $length); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap72.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap72.php new file mode 100644 index 000000000..599e45f9d --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap72.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php84 as p; + +if (extension_loaded('mbstring')) { + if (!function_exists('mb_ucfirst')) { + /** @return string|false */ + function mb_ucfirst(?string $string, ?string $encoding = null) { return p\Php84::mb_ucfirst((string) $string, $encoding); } + } + + if (!function_exists('mb_lcfirst')) { + /** @return string|false */ + function mb_lcfirst(?string $string, ?string $encoding = null) { return p\Php84::mb_lcfirst((string) $string, $encoding); } + } + + if (!function_exists('mb_trim')) { + /** @return string|false */ + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_trim((string) $string, $characters, $encoding); } + } + + if (!function_exists('mb_ltrim')) { + /** @return string|false */ + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_ltrim((string) $string, $characters, $encoding); } + } + + if (!function_exists('mb_rtrim')) { + /** @return string|false */ + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_rtrim((string) $string, $characters, $encoding); } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap82.php b/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap82.php new file mode 100644 index 000000000..216ad021f --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/bootstrap82.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php84 as p; + +if (\PHP_VERSION_ID >= 80400) { + return; +} + +if (extension_loaded('intl') && !function_exists('grapheme_str_split')) { + function grapheme_str_split(string $string, int $length = 1): array|false { return p\Php84::grapheme_str_split($string, $length); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php84/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-php84/composer.json new file mode 100644 index 000000000..8bbc28125 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php84/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php84", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php84\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-php85/LICENSE new file mode 100644 index 000000000..bc38d714e --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/Php85.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/Php85.php new file mode 100644 index 000000000..d1f038a10 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/Php85.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php85; + +/** + * @author Pierre Ambroise + * @author Alexander Schranz + * + * @internal + */ +final class Php85 +{ + public static function get_error_handler(): ?callable + { + $handler = set_error_handler(null); + restore_error_handler(); + + return $handler; + } + + public static function get_exception_handler(): ?callable + { + $handler = set_exception_handler(null); + restore_exception_handler(); + + return $handler; + } + + public static function array_first(array $array) + { + foreach ($array as $value) { + return $value; + } + + return null; + } + + public static function array_last(array $array) + { + return $array ? current(\array_slice($array, -1)) : null; + } + + // Taken from CLDR 44 (ICU 74); the exact set is ICU-version dependent and won't match every version of the native locale_is_right_to_left(). + private const RTL_SCRIPTS = [ + 'Adlm' => true, 'Arab' => true, 'Armi' => true, 'Avst' => true, + 'Chrs' => true, 'Cprt' => true, 'Elym' => true, 'Hatr' => true, + 'Hebr' => true, 'Hung' => true, 'Khar' => true, 'Lydi' => true, + 'Mand' => true, 'Mani' => true, 'Mend' => true, 'Merc' => true, + 'Mero' => true, 'Narb' => true, 'Nbat' => true, 'Nkoo' => true, + 'Orkh' => true, 'Ougr' => true, 'Palm' => true, 'Phli' => true, + 'Phlp' => true, 'Phnx' => true, 'Prti' => true, 'Rohg' => true, + 'Samr' => true, 'Sarb' => true, 'Sogd' => true, 'Sogo' => true, + 'Syrc' => true, 'Thaa' => true, 'Yezi' => true, + ]; + + private const LANG_TO_SCRIPT = [ + 'aeb' => 'Arab', + 'ar' => 'Arab', + 'arc' => 'Armi', + 'arq' => 'Arab', + 'ary' => 'Arab', + 'arz' => 'Arab', + 'bal' => 'Arab', + 'bgn' => 'Arab', + 'ckb' => 'Arab', + 'dv' => 'Thaa', + 'fa' => 'Arab', + 'glk' => 'Arab', + 'haz' => 'Arab', + 'he' => 'Hebr', + 'iw' => 'Hebr', + 'kas' => 'Arab', + 'ks' => 'Arab', + 'lrc' => 'Arab', + 'mzn' => 'Arab', + 'nqo' => 'Nkoo', + 'pnb' => 'Arab', + 'ps' => 'Arab', + 'rhg' => 'Rohg', + 'sam' => 'Samr', + 'sd' => 'Arab', + 'sdh' => 'Arab', + 'skr' => 'Arab', + 'syr' => 'Syrc', + 'ug' => 'Arab', + 'ur' => 'Arab', + 'yi' => 'Hebr', + ]; + + public static function locale_is_right_to_left(string $locale): bool + { + if ('' === $locale) { + $locale = \Locale::getDefault(); + } + + $parts = preg_split('/[_-]/', substr($locale, 0, strcspn($locale, '@.'))); + $language = strtolower($parts[0]); + + if (isset($parts[1]) && 4 === \strlen($parts[1]) && ctype_alpha($parts[1])) { + return isset(self::RTL_SCRIPTS[ucfirst(strtolower($parts[1]))]); + } + + return isset(self::LANG_TO_SCRIPT[$language]) && isset(self::RTL_SCRIPTS[self::LANG_TO_SCRIPT[$language]]); + } + + public static function grapheme_levenshtein(string $s1, string $s2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') + { + if ($insertion_cost <= 0 || $insertion_cost > 1073741823) { + throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost) must be greater than 0 and less than or equal to 1073741823'); + } + if ($replacement_cost <= 0 || $replacement_cost > 1073741823) { + throw new \ValueError('grapheme_levenshtein(): Argument #4 ($replacement_cost) must be greater than 0 and less than or equal to 1073741823'); + } + if ($deletion_cost <= 0 || $deletion_cost > 1073741823) { + throw new \ValueError('grapheme_levenshtein(): Argument #5 ($deletion_cost) must be greater than 0 and less than or equal to 1073741823'); + } + + if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) { + return false; + } + + $regex = ((float) \PCRE_VERSION >= 10.44) + ? '\X' + : '(?:\r\n|[\x{1F1E6}-\x{1F1FF}][\x{1F1E6}-\x{1F1FF}]?|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*(?:\x{200D}(?:[\x{1F1E6}-\x{1F1FF}]|[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가-힣]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Mc}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{1D165}\x{1D16E}-\x{1D172}\x{1F3FB}-\x{1F3FF}\x{FE0E}-\x{FE0F}\x{E0020}-\x{E007F}]*)*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + + preg_match_all('/'.$regex.'/u', $s1, $s1); + preg_match_all('/'.$regex.'/u', $s2, $s2); + + $s1 = $s1[0]; + $s2 = $s2[0]; + $l1 = \count($s1); + $l2 = \count($s2); + + // Keep the rows as short as possible. Reversing the transformation + // swaps the meaning of insertion and deletion. + if ($l1 < $l2) { + [$s1, $s2] = [$s2, $s1]; + [$l1, $l2] = [$l2, $l1]; + [$insertion_cost, $deletion_cost] = [$deletion_cost, $insertion_cost]; + } + + if (0 === $l2) { + return $l1 * $deletion_cost; + } + + // Graphemes are equal when the collator says so, as with ucol_strcoll() in intl + try { + $collator = new \Collator('' === $locale ? 'root' : $locale); + } catch (\IntlException $e) { + return false; + } + $s1 = array_map([$collator, 'getSortKey'], $s1); + $s2 = array_map([$collator, 'getSortKey'], $s2); + + $previousRow = $currentRow = array_fill(0, $l2 + 1, 0); + for ($j = 1; $j <= $l2; ++$j) { + $previousRow[$j] = $previousRow[$j - 1] + $insertion_cost; + } + + for ($i = 1; $i <= $l1; ++$i) { + $currentRow[0] = $previousRow[0] + $deletion_cost; + + for ($j = 1; $j <= $l2; ++$j) { + $cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : $replacement_cost; + $currentRow[$j] = min($previousRow[$j] + $deletion_cost, $currentRow[$j - 1] + $insertion_cost, $previousRow[$j - 1] + $cost); + } + + [$previousRow, $currentRow] = [$currentRow, $previousRow]; + } + + return $previousRow[$l2]; + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/README.md b/data/web/inc/lib/vendor/symfony/polyfill-php85/README.md new file mode 100644 index 000000000..36e1ecdb9 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/README.md @@ -0,0 +1,20 @@ +Symfony Polyfill / Php85 +======================== + +This component provides features added to PHP 8.5 core: + +- [`get_error_handler` and `get_exception_handler`](https://wiki.php.net/rfc/get-error-exception-handler) +- [`NoDiscard`](https://wiki.php.net/rfc/marking_return_value_as_important) +- [`array_first` and `array_last`](https://wiki.php.net/rfc/array_first_last) +- [`DelayedTargetValidation`](https://wiki.php.net/rfc/delayedtargetvalidation_attribute) +- [`Filter\FilterException class`](https://wiki.php.net/rfc/filter_throw_on_failure) +- [`Filter\FilterFailedException class`](https://wiki.php.net/rfc/filter_throw_on_failure) +- [`locale_is_right_to_left`](https://php.watch/versions/8.5/locale_is_right_to_left-Locale-isRightToleft) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/DelayedTargetValidation.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/DelayedTargetValidation.php new file mode 100644 index 000000000..42d31bf38 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/DelayedTargetValidation.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80500) { + // @author Daniel Scherzer + #[Attribute(Attribute::TARGET_ALL)] + final class DelayedTargetValidation + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/Filter/FilterException.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/Filter/FilterException.php new file mode 100644 index 000000000..e3183ef58 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/Filter/FilterException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Filter; + +if (\PHP_VERSION_ID < 80500) { + class FilterException extends \Exception + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/Filter/FilterFailedException.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/Filter/FilterFailedException.php new file mode 100644 index 000000000..479f7dd18 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/Filter/FilterFailedException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Filter; + +if (\PHP_VERSION_ID < 80500) { + class FilterFailedException extends FilterException + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/NoDiscard.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/NoDiscard.php new file mode 100644 index 000000000..bc2c36d9d --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/Resources/stubs/NoDiscard.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80500) { + #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] + final class NoDiscard + { + public ?string $message; + + public function __construct(?string $message = null) + { + $this->message = $message; + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/bootstrap.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/bootstrap.php new file mode 100644 index 000000000..0aab93b80 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/bootstrap.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php85 as p; + +if (\PHP_VERSION_ID >= 80500) { + return; +} + +if (!function_exists('get_error_handler')) { + function get_error_handler(): ?callable { return p\Php85::get_error_handler(); } +} + +if (!function_exists('get_exception_handler')) { + function get_exception_handler(): ?callable { return p\Php85::get_exception_handler(); } +} + +if (!function_exists('array_first')) { + function array_first(array $array) { return p\Php85::array_first($array); } +} + +if (!function_exists('array_last')) { + function array_last(array $array) { return p\Php85::array_last($array); } +} + +if (extension_loaded('intl') && !function_exists('locale_is_right_to_left')) { + function locale_is_right_to_left(string $locale): bool { return p\Php85::locale_is_right_to_left($locale); } +} + +if (\PHP_VERSION_ID >= 80000) { + require __DIR__.'/bootstrap80.php'; + + return; +} + +if (!class_exists('ValueError', false)) { + class ValueError extends Error + { + } +} + +if (extension_loaded('intl') && !function_exists('grapheme_levenshtein')) { + function grapheme_levenshtein(string $string1, string $string2, ?int $insertion_cost = 1, ?int $replacement_cost = 1, ?int $deletion_cost = 1, string $locale = '') { return p\Php85::grapheme_levenshtein($string1, $string2, (int) $insertion_cost, (int) $replacement_cost, (int) $deletion_cost, $locale); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/bootstrap80.php b/data/web/inc/lib/vendor/symfony/polyfill-php85/bootstrap80.php new file mode 100644 index 000000000..8a0e054e1 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/bootstrap80.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php85 as p; + +if (\PHP_VERSION_ID >= 80500) { + return; +} + +if (extension_loaded('intl') && !function_exists('grapheme_levenshtein')) { + function grapheme_levenshtein(string $string1, string $string2, ?int $insertion_cost = 1, ?int $replacement_cost = 1, ?int $deletion_cost = 1, string $locale = ''): int|false { return p\Php85::grapheme_levenshtein($string1, $string2, (int) $insertion_cost, (int) $replacement_cost, (int) $deletion_cost, $locale); } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-php85/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-php85/composer.json new file mode 100644 index 000000000..ef0ec8d18 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-php85/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/polyfill-php85", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php85\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-time/LICENSE b/data/web/inc/lib/vendor/symfony/polyfill-time/LICENSE new file mode 100644 index 000000000..36d6fdc38 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-time/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2026-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/data/web/inc/lib/vendor/symfony/polyfill-time/README.md b/data/web/inc/lib/vendor/symfony/polyfill-time/README.md new file mode 100644 index 000000000..73efc2657 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-time/README.md @@ -0,0 +1,21 @@ +Symfony Polyfill / Time +======================= + +This component provides the [`Time` API](https://wiki.php.net/rfc/duration_class) +added to PHP 8.6 core, for PHP >= 8.1: + +- `Time\TimeException`, `Time\Duration` + +The phpt tests of the native implementation, borrowed from php-src, run against +the polyfill as part of the test suite. One divergence remains: `Duration` is a +plain final class with readonly properties instead of a `readonly` class, so +`ReflectionClass::newInstanceWithoutConstructor()` succeeds where the native +class rejects it. Dynamic properties are rejected as they are natively. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/data/web/inc/lib/vendor/symfony/polyfill-time/Resources/stubs/Time/Duration.php b/data/web/inc/lib/vendor/symfony/polyfill-time/Resources/stubs/Time/Duration.php new file mode 100644 index 000000000..47f01b108 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-time/Resources/stubs/Time/Duration.php @@ -0,0 +1,489 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Time; + +if (\PHP_VERSION_ID < 80600) { + /** + * @see https://wiki.php.net/rfc/duration_class + */ + final class Duration + { + private const NANOS_PER_SECOND = 1_000_000_000; + + /** + * Maximum seconds accepted by Duration. + * + * One second is reserved to avoid overflowing when arithmetic operations + * temporarily require carrying nanoseconds into the seconds component. + */ + private const MAX_SECONDS = \PHP_INT_SIZE >= 8 ? 9_223_372_035 : \PHP_INT_MAX; + + private const RANGE_ERROR = \PHP_INT_SIZE >= 8 + ? 'The maximum representable range is 9_223_372_035 seconds (roughly 292 years)' + : 'The maximum representable range is 2_147_483_647 seconds (roughly 68 years)'; + + /** + * Regular expression to parse an ISO-8601 duration string. + * + * Year, Month, Week and Day components are intentionally not supported. + * The biggest supported component is Hour. + */ + private const REGEXP_ISO8601 = '@^ + PT + (?=(?:\d+H|\d+M|\d+S)) # look ahead to avoid PT without argument + (?:(?\d+)H)? + (?:(?\d+)M)? + (?:(?\d+)S)? + $@x'; + + /** + * Regular expression matching any ISO-8601 duration, including the + * date components that Duration does not support. + */ + private const REGEXP_ISO8601_ANY = '@^ + P + (?:(?\d+)Y)? + (?:(?\d+)M)? + (?:(?\d+)W)? + (?:(?\d+)D)? + (?:T(?:\d+H)?(?:\d+M)?(?:\d+S)?)? + $@x'; + + private function __construct( + public readonly int $seconds, + public readonly int $nanoseconds, + public readonly bool $negative, + ) {} + + /** + * Create a duration representing $seconds seconds and $nanoseconds nanoseconds. Neither parameter + * may be negative. $nanoseconds must be less than 1_000_000_000 (the number of nanoseconds in a + * second). + * + * This constructor creates a Duration from its “atomic” components. + * + * @throws TimeException + */ + public static function fromSeconds(int $seconds, int $nanoseconds = 0): self + { + if ($seconds < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($seconds) must be greater than or equal to 0'); + } + + if ($nanoseconds < 0) { + throw new \ValueError(__METHOD__.'(): Argument #2 ($nanoseconds) must be greater than or equal to 0'); + } + + if ($nanoseconds >= self::NANOS_PER_SECOND) { + throw new \ValueError(__METHOD__.'(): Argument #2 ($nanoseconds) must be less than 1_000_000_000'); + } + + return self::create($seconds, $nanoseconds, false); + } + + /** + * Create a duration representing $nanoseconds nanoseconds. $nanoseconds must not be negative. + * + * @throws TimeException + */ + public static function fromNanoseconds(int $nanoseconds): self + { + if ($nanoseconds < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($nanoseconds) must be greater than or equal to 0'); + } + + return self::create(intdiv($nanoseconds, self::NANOS_PER_SECOND), $nanoseconds % self::NANOS_PER_SECOND, false); + } + + /** + * Create a duration representing $microseconds microseconds. $microseconds must not be negative. + * + * @throws TimeException + */ + public static function fromMicroseconds(int $microseconds): self + { + if ($microseconds < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($microseconds) must be greater than or equal to 0'); + } + + return self::create(intdiv($microseconds, 1_000_000), ($microseconds % 1_000_000) * 1_000, false); + } + + /** + * Create a duration representing $milliseconds milliseconds. $milliseconds must not be negative. + * + * @throws TimeException + */ + public static function fromMilliseconds(int $milliseconds): self + { + if ($milliseconds < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($milliseconds) must be greater than or equal to 0'); + } + + return self::create(intdiv($milliseconds, 1_000), ($milliseconds % 1_000) * 1_000_000, false); + } + + /** + * Create a duration representing $minutes minutes. $minutes must not be negative. + * + * @throws TimeException + */ + public static function fromMinutes(int $minutes): self + { + if ($minutes < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($minutes) must be greater than or equal to 0'); + } + + if ($minutes > intdiv(self::MAX_SECONDS, 60)) { + throw new TimeException(self::RANGE_ERROR); + } + + return self::create($minutes * 60, 0, false); + } + + /** + * Create a duration representing $hours hours. $hours must not be negative. + * + * @throws TimeException + */ + public static function fromHours(int $hours): self + { + if ($hours < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($hours) must be greater than or equal to 0'); + } + + if ($hours > intdiv(self::MAX_SECONDS, 3_600)) { + throw new TimeException(self::RANGE_ERROR); + } + + return self::create($hours * 3_600, 0, false); + } + + /** + * Parse a ISO-8601 period. ISO-8601 periods with a date component will be rejected. + * The biggest allowed component is H. + * + * @throws TimeException + */ + public static function fromIso8601DurationString(string $specification): self + { + if (!preg_match(self::REGEXP_ISO8601, $specification, $parts)) { + throw new TimeException(self::iso8601Error($specification)); + } + + $hours = (int) ($parts['hour'] ?? 0); + $minutes = (int) ($parts['minute'] ?? 0); + $seconds = (int) ($parts['second'] ?? 0); + + // each step is checked against the maximum so that no intermediate value can overflow + if ($hours > intdiv(self::MAX_SECONDS, 3_600)) { + throw new TimeException(self::RANGE_ERROR); + } + + $total = $hours * 3_600; + + if ($minutes > intdiv(self::MAX_SECONDS - $total, 60)) { + throw new TimeException(self::RANGE_ERROR); + } + + $total += $minutes * 60; + + if ($seconds > self::MAX_SECONDS - $total) { + throw new TimeException(self::RANGE_ERROR); + } + + return self::create($total + $seconds, 0, false); + } + + /** + * Negates the duration. + * + * @return self -$this + */ + public function negate(): self + { + if (0 === $this->seconds && 0 === $this->nanoseconds) { + return $this; + } + + return new self($this->seconds, $this->nanoseconds, !$this->negative); + } + + /** + * Returns the absolute value of the duration. + * + * @return self abs($this) + */ + public function absolute(): self + { + if (!$this->negative) { + return $this; + } + + return new self($this->seconds, $this->nanoseconds, false); + } + + /** + * Add the given duration to the duration. + * + * @return self $this + $duration + * + * @throws TimeException + */ + public function add(self $duration): self + { + $seconds = $this->negative ? -$this->seconds : $this->seconds; + $nanoseconds = $this->negative ? -$this->nanoseconds : $this->nanoseconds; + $addSeconds = $duration->negative ? -$duration->seconds : $duration->seconds; + $addNanoseconds = $duration->negative ? -$duration->nanoseconds : $duration->nanoseconds; + + if ($this->negative === $duration->negative && $this->seconds > self::MAX_SECONDS - $duration->seconds) { + throw new TimeException(self::RANGE_ERROR); + } + + $seconds += $addSeconds; + $nanoseconds += $addNanoseconds; + + if ($nanoseconds >= self::NANOS_PER_SECOND || $nanoseconds <= -self::NANOS_PER_SECOND) { + $carry = intdiv($nanoseconds, self::NANOS_PER_SECOND); + $nanoseconds %= self::NANOS_PER_SECOND; + + if ($carry > 0 ? $seconds > self::MAX_SECONDS - $carry : $seconds < -self::MAX_SECONDS - $carry) { + throw new TimeException(self::RANGE_ERROR); + } + + $seconds += $carry; + } + + if (0 < $seconds && 0 > $nanoseconds) { + --$seconds; + $nanoseconds += self::NANOS_PER_SECOND; + } + + if (0 > $seconds && 0 < $nanoseconds) { + ++$seconds; + $nanoseconds -= self::NANOS_PER_SECOND; + } + + return self::create(abs($seconds), abs($nanoseconds), 0 > $seconds || 0 > $nanoseconds); + } + + /** + * Subtract the given duration from the duration. + * + * @return self $this - $duration + * + * @throws TimeException + */ + public function sub(self $duration): self + { + return $this->add($duration->negate()); + } + + /** + * Multiply the length of the duration by the given factor. $factor must not be negative. + * + * @return self $this * $factor + * + * @throws TimeException + */ + public function multiplyBy(int $factor): self + { + if ($factor < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($factor) must be greater than or equal to 0'); + } + + if (0 === $factor) { + return new self(0, 0, false); + } + + if (1 === $factor) { + return $this; + } + + // double-and-add: multiplying the nanoseconds directly would overflow + $seconds = 0; + $nanoseconds = 0; + $addSeconds = $this->seconds; + $addNanoseconds = $this->nanoseconds; + + while (true) { + if (1 & $factor) { + [$seconds, $nanoseconds] = self::addMagnitudes($seconds, $nanoseconds, $addSeconds, $addNanoseconds); + } + + if (0 === $factor >>= 1) { + break; + } + + // the remaining factor is not zero, so an out of range double is out of range for the result too + [$addSeconds, $addNanoseconds] = self::addMagnitudes($addSeconds, $addNanoseconds, $addSeconds, $addNanoseconds); + } + + return self::create($seconds, $nanoseconds, $this->negative); + } + + /** + * Divide the length of the duration by the given divisor. $divisor must be positive. + * + * Fractional nanoseconds will be truncated. + * + * @return self $this / $divisor + * + * @throws TimeException + */ + public function divideBy(int $divisor): self + { + if ($divisor < 0) { + throw new \ValueError(__METHOD__.'(): Argument #1 ($divisor) must be greater than or equal to 0'); + } + + if (0 === $divisor) { + throw new \DivisionByZeroError('Division by zero'); + } + + if (1 === $divisor) { + return $this; + } + + $remainder = $this->seconds % $divisor; + + $nanoseconds = $remainder > intdiv(\PHP_INT_MAX - $this->nanoseconds, self::NANOS_PER_SECOND) + ? self::divideNanoseconds($remainder, $this->nanoseconds, $divisor) + : intdiv($this->nanoseconds + $remainder * self::NANOS_PER_SECOND, $divisor); + + return self::create(intdiv($this->seconds, $divisor), $nanoseconds, $this->negative); + } + + /** + * Returns -1, 0, 1 if $a is less than, equal to, or greater than $b respectively. + */ + public static function compare(self $a, self $b): int + { + if ($a->negative !== $b->negative) { + return $a->negative ? -1 : 1; + } + + $comparison = $a->seconds <=> $b->seconds ?: $a->nanoseconds <=> $b->nanoseconds; + + return $a->negative ? -$comparison : $comparison; + } + + /** + * The native class is declared readonly, which forbids dynamic properties. + */ + public function __set(string $name, mixed $value): void + { + throw new \Error(\sprintf('Cannot create dynamic property %s::$%s', self::class, $name)); + } + + /** + * Adds two non-negative (seconds, nanoseconds) pairs. + * + * @return array{0: int, 1: int} + * + * @throws TimeException when the sum is out of the representable range + */ + private static function addMagnitudes(int $seconds, int $nanoseconds, int $addSeconds, int $addNanoseconds): array + { + if ($seconds > self::MAX_SECONDS - $addSeconds) { + throw new TimeException(self::RANGE_ERROR); + } + + $seconds += $addSeconds; + // the sum of two nanoseconds components is always below 2**31 + $nanoseconds += $addNanoseconds; + + if ($nanoseconds >= self::NANOS_PER_SECOND) { + if (self::MAX_SECONDS === $seconds) { + throw new TimeException(self::RANGE_ERROR); + } + + ++$seconds; + $nanoseconds -= self::NANOS_PER_SECOND; + } + + return [$seconds, $nanoseconds]; + } + + /** + * Returns intdiv($remainder * 1_000_000_000 + $nanoseconds, $divisor) for + * a $remainder that is lower than $divisor. + * + * The dividend does not fit in an integer on 32 bit platforms, so the + * division is done one decimal digit at a time. Every intermediate value + * stays below 2**53, where floats represent integers exactly. + */ + private static function divideNanoseconds(int $remainder, int $nanoseconds, int $divisor): int + { + $quotient = 0; + $rest = (float) $remainder; + + foreach (str_split(str_pad((string) $nanoseconds, 9, '0', \STR_PAD_LEFT)) as $digit) { + $rest = $rest * 10 + (int) $digit; + $digit = (int) ($rest / $divisor); + $rest -= $digit * $divisor; + + // the float division may be off by one in either direction + while (0 > $rest) { + --$digit; + $rest += $divisor; + } + while ($rest >= $divisor) { + ++$digit; + $rest -= $divisor; + } + + $quotient = $quotient * 10 + $digit; + } + + return $quotient; + } + + /** + * @throws TimeException when the duration is out of the representable range + */ + private static function create(int $seconds, int $nanoseconds, bool $negative): self + { + if ($seconds > self::MAX_SECONDS) { + throw new TimeException(self::RANGE_ERROR); + } + + // a zero duration is never negative + return new self($seconds, $nanoseconds, $negative && (0 !== $seconds || 0 !== $nanoseconds)); + } + + /** + * Mirrors the error reporting of timelib_duration_create_from_iso8601string(). + */ + private static function iso8601Error(string $specification): string + { + if (preg_match(self::REGEXP_ISO8601_ANY, $specification, $parts)) { + if ((int) ($parts['year'] ?? 0) || (int) ($parts['month'] ?? 0) || (int) ($parts['week'] ?? 0) || (int) ($parts['day'] ?? 0)) { + return 'The ISO 8601 duration string may only contain the time (T) aspect'; + } + + return 'The ISO 8601 duration string could not be parsed'; + } + + if (str_contains($specification, '/')) { + return 'The ISO 8601 duration string may only contain the period (P) aspect'; + } + + if (!str_contains($specification, 'P') && false !== @strtotime($specification)) { + return 'The ISO 8601 duration string is missing the period (P) aspect'; + } + + return 'The ISO 8601 duration string could not be parsed'; + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-time/Resources/stubs/Time/TimeException.php b/data/web/inc/lib/vendor/symfony/polyfill-time/Resources/stubs/Time/TimeException.php new file mode 100644 index 000000000..b6e328a7b --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-time/Resources/stubs/Time/TimeException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Time; + +if (\PHP_VERSION_ID < 80600) { + class TimeException extends \Exception + { + } +} diff --git a/data/web/inc/lib/vendor/symfony/polyfill-time/composer.json b/data/web/inc/lib/vendor/symfony/polyfill-time/composer.json new file mode 100644 index 000000000..f6fcb6423 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/polyfill-time/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-time", + "type": "library", + "description": "Symfony polyfill for the Time API added to PHP 8.6", + "keywords": ["polyfill", "shim", "compatibility", "portable", "time", "duration"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ignace Philippe Nyamagana Butera", + "email": "nyamsprod@gmail.com" + }, + { + "name": "Tim Düsterhus", + "email": "tim@tideways-gmbh.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Time\\": "" }, + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/data/web/inc/lib/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/data/web/inc/lib/vendor/symfony/translation-contracts/Test/TranslatorTest.php index 756228af5..c86efe17d 100644 --- a/data/web/inc/lib/vendor/symfony/translation-contracts/Test/TranslatorTest.php +++ b/data/web/inc/lib/vendor/symfony/translation-contracts/Test/TranslatorTest.php @@ -11,7 +11,10 @@ namespace Symfony\Contracts\Translation\Test; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; +use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorTrait; @@ -24,7 +27,7 @@ use Symfony\Contracts\Translation\TranslatorTrait; * * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 * - * The goal to cover all languages is to far fetched so this test case is smaller. + * The goal to cover all languages is too far fetched so this test case is smaller. * * @author Clemens Tolboom clemens@build2be.nl */ @@ -45,7 +48,7 @@ class TranslatorTest extends TestCase public function getTranslator(): TranslatorInterface { - return new class() implements TranslatorInterface { + return new class implements TranslatorInterface { use TranslatorTrait; }; } @@ -53,6 +56,7 @@ class TranslatorTest extends TestCase /** * @dataProvider getTransTests */ + #[DataProvider('getTransTests')] public function testTrans($expected, $id, $parameters) { $translator = $this->getTranslator(); @@ -63,6 +67,7 @@ class TranslatorTest extends TestCase /** * @dataProvider getTransChoiceTests */ + #[DataProvider('getTransChoiceTests')] public function testTransChoiceWithExplicitLocale($expected, $id, $number) { $translator = $this->getTranslator(); @@ -75,6 +80,8 @@ class TranslatorTest extends TestCase * * @dataProvider getTransChoiceTests */ + #[DataProvider('getTransChoiceTests')] + #[RequiresPhpExtension('intl')] public function testTransChoiceWithDefaultLocale($expected, $id, $number) { $translator = $this->getTranslator(); @@ -85,6 +92,7 @@ class TranslatorTest extends TestCase /** * @dataProvider getTransChoiceTests */ + #[DataProvider('getTransChoiceTests')] public function testTransChoiceWithEnUsPosix($expected, $id, $number) { $translator = $this->getTranslator(); @@ -103,6 +111,7 @@ class TranslatorTest extends TestCase /** * @requires extension intl */ + #[RequiresPhpExtension('intl')] public function testGetLocaleReturnsDefaultLocaleIfNotSet() { $translator = $this->getTranslator(); @@ -116,10 +125,12 @@ class TranslatorTest extends TestCase public static function getTransTests() { - return [ - ['Symfony is great!', 'Symfony is great!', []], - ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']], - ]; + yield ['Symfony is great!', 'Symfony is great!', []]; + yield ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']]; + + if (class_exists(TranslatableMessage::class)) { + yield ['He said "Symfony is awesome!".', 'He said "%what%".', ['%what%' => new TranslatableMessage('Symfony is %what%!', ['%what%' => 'awesome'])]]; + } } public static function getTransChoiceTests() @@ -139,6 +150,7 @@ class TranslatorTest extends TestCase /** * @dataProvider getInterval */ + #[DataProvider('getInterval')] public function testInterval($expected, $number, $interval) { $translator = $this->getTranslator(); @@ -164,6 +176,7 @@ class TranslatorTest extends TestCase /** * @dataProvider getChooseTests */ + #[DataProvider('getChooseTests')] public function testChoose($expected, $id, $number, $locale = null) { $translator = $this->getTranslator(); @@ -181,6 +194,7 @@ class TranslatorTest extends TestCase /** * @dataProvider getNonMatchingMessages */ + #[DataProvider('getNonMatchingMessages')] public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) { $translator = $this->getTranslator(); @@ -296,6 +310,7 @@ class TranslatorTest extends TestCase /** * @dataProvider failingLangcodes */ + #[DataProvider('failingLangcodes')] public function testFailedLangcodes($nplural, $langCodes) { $matrix = $this->generateTestData($langCodes); @@ -305,6 +320,7 @@ class TranslatorTest extends TestCase /** * @dataProvider successLangcodes */ + #[DataProvider('successLangcodes')] public function testLangcodes($nplural, $langCodes) { $matrix = $this->generateTestData($langCodes); @@ -333,7 +349,7 @@ class TranslatorTest extends TestCase * This both depends on a complete list trying to add above as understanding * the plural rules of the current failing languages. * - * @return array with nplural together with langcodes + * @return array With nplural together with langcodes */ public static function failingLangcodes(): array { @@ -359,14 +375,14 @@ class TranslatorTest extends TestCase if ($expectSuccess) { $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); } else { - $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + $this->assertNotCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms."); } } } protected function generateTestData($langCodes) { - $translator = new class() { + $translator = new class { use TranslatorTrait { getPluralizationRule as public; } diff --git a/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatableInterface.php b/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatableInterface.php index 47fd6fa02..8554697ec 100644 --- a/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatableInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatableInterface.php @@ -16,5 +16,5 @@ namespace Symfony\Contracts\Translation; */ interface TranslatableInterface { - public function trans(TranslatorInterface $translator, string $locale = null): string; + public function trans(TranslatorInterface $translator, ?string $locale = null): string; } diff --git a/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorInterface.php b/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorInterface.php index 018db07eb..7fa69878f 100644 --- a/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorInterface.php @@ -59,7 +59,7 @@ interface TranslatorInterface * * @throws \InvalidArgumentException If the locale contains invalid characters */ - public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string; + public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string; /** * Returns the default locale. diff --git a/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorTrait.php b/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorTrait.php index e3b0adff0..06d3e3059 100644 --- a/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorTrait.php +++ b/data/web/inc/lib/vendor/symfony/translation-contracts/TranslatorTrait.php @@ -35,12 +35,18 @@ trait TranslatorTrait return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); } - public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { if (null === $id || '' === $id) { return ''; } + foreach ($parameters as $k => $v) { + if ($v instanceof TranslatableInterface) { + $parameters[$k] = $v->trans($this, $locale); + } + } + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { return strtr($id, $parameters); } @@ -56,22 +62,22 @@ trait TranslatorTrait } $intervalRegexp = <<<'EOF' -/^(?P - ({\s* - (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) - \s*}) + /^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) - | + | - (?P[\[\]]) - \s* - (?P-Inf|\-?\d+(\.\d+)?) - \s*,\s* - (?P\+?Inf|\-?\d+(\.\d+)?) - \s* - (?P[\[\]]) -)\s*(?P.*?)$/xs -EOF; + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) + )\s*(?P.*?)$/xs + EOF; $standardRules = []; foreach ($parts as $part) { @@ -111,7 +117,7 @@ EOF; return strtr($standardRules[0], $parameters); } - $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); + $message = \sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number); if (class_exists(InvalidArgumentException::class)) { throw new InvalidArgumentException($message); diff --git a/data/web/inc/lib/vendor/symfony/translation-contracts/composer.json b/data/web/inc/lib/vendor/symfony/translation-contracts/composer.json index 213b5cda8..07b0d870c 100644 --- a/data/web/inc/lib/vendor/symfony/translation-contracts/composer.json +++ b/data/web/inc/lib/vendor/symfony/translation-contracts/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.7-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/data/web/inc/lib/vendor/symfony/translation/CHANGELOG.md b/data/web/inc/lib/vendor/symfony/translation/CHANGELOG.md index 5f9098c07..fdd0b76b3 100644 --- a/data/web/inc/lib/vendor/symfony/translation/CHANGELOG.md +++ b/data/web/inc/lib/vendor/symfony/translation/CHANGELOG.md @@ -1,6 +1,43 @@ CHANGELOG ========= +7.4 +--- + + * Make the extractor alias optional + * Deprecate `TranslatableMessage::__toString` + * Add `Symfony\Component\Translation\StaticMessage` + +7.3 +--- + + * Add `Translator::addGlobalParameter()` to allow defining global translation parameters + +7.2 +--- + + * Deprecate `ProviderFactoryTestCase`, extend `AbstractProviderFactoryTestCase` instead + + The `testIncompleteDsnException()` test is no longer provided by default. If you make use of it by implementing the `incompleteDsnProvider()` data providers, + you now need to use the `IncompleteDsnTestTrait`. + + * Make `ProviderFactoryTestCase` and `ProviderTestCase` compatible with PHPUnit 10+ + * Add `lint:translations` command + * Deprecate passing an escape character to `CsvFileLoader::setCsvControl()` + * Make Xliff 2.0 attributes in segment element available as `segment-attributes` + metadata returned by `XliffFileLoader` and make `XliffFileDumper` write them to the file + +7.1 +--- + + * Mark class `DataCollectorTranslator` as `final` + +7.0 +--- + + * Remove `PhpStringTokenParser` + * Remove `PhpExtractor` in favor of `PhpAstExtractor` + 6.4 --- diff --git a/data/web/inc/lib/vendor/symfony/translation/Catalogue/AbstractOperation.php b/data/web/inc/lib/vendor/symfony/translation/Catalogue/AbstractOperation.php index 7dff58ff4..2143b4f04 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Catalogue/AbstractOperation.php +++ b/data/web/inc/lib/vendor/symfony/translation/Catalogue/AbstractOperation.php @@ -30,9 +30,7 @@ abstract class AbstractOperation implements OperationInterface public const NEW_BATCH = 'new'; public const ALL_BATCH = 'all'; - protected $source; - protected $target; - protected $result; + protected MessageCatalogue $result; /** * This array stores 'all', 'new' and 'obsolete' messages for all valid domains. @@ -55,21 +53,21 @@ abstract class AbstractOperation implements OperationInterface * * @var array The array that stores 'all', 'new' and 'obsolete' messages */ - protected $messages; + protected array $messages; private array $domains; /** * @throws LogicException */ - public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) - { + public function __construct( + protected MessageCatalogueInterface $source, + protected MessageCatalogueInterface $target, + ) { if ($source->getLocale() !== $target->getLocale()) { throw new LogicException('Operated catalogues must belong to the same locale.'); } - $this->source = $source; - $this->target = $target; $this->result = new MessageCatalogue($source->getLocale()); $this->messages = []; } @@ -96,8 +94,8 @@ abstract class AbstractOperation implements OperationInterface public function getMessages(string $domain): array { - if (!\in_array($domain, $this->getDomains())) { - throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + if (!\in_array($domain, $this->getDomains(), true)) { + throw new InvalidArgumentException(\sprintf('Invalid domain: "%s".', $domain)); } if (!isset($this->messages[$domain][self::ALL_BATCH])) { @@ -109,8 +107,8 @@ abstract class AbstractOperation implements OperationInterface public function getNewMessages(string $domain): array { - if (!\in_array($domain, $this->getDomains())) { - throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + if (!\in_array($domain, $this->getDomains(), true)) { + throw new InvalidArgumentException(\sprintf('Invalid domain: "%s".', $domain)); } if (!isset($this->messages[$domain][self::NEW_BATCH])) { @@ -122,8 +120,8 @@ abstract class AbstractOperation implements OperationInterface public function getObsoleteMessages(string $domain): array { - if (!\in_array($domain, $this->getDomains())) { - throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain)); + if (!\in_array($domain, $this->getDomains(), true)) { + throw new InvalidArgumentException(\sprintf('Invalid domain: "%s".', $domain)); } if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) { @@ -149,7 +147,7 @@ abstract class AbstractOperation implements OperationInterface */ public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void { - // If MessageFormatter class does not exists, intl domains are not supported. + // If MessageFormatter class does not exist, intl domains are not supported. if (!class_exists(\MessageFormatter::class)) { return; } @@ -160,7 +158,7 @@ abstract class AbstractOperation implements OperationInterface self::OBSOLETE_BATCH => $this->getObsoleteMessages($domain), self::NEW_BATCH => $this->getNewMessages($domain), self::ALL_BATCH => $this->getMessages($domain), - default => throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH)), + default => throw new \InvalidArgumentException(\sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH)), }; if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) { @@ -172,6 +170,12 @@ abstract class AbstractOperation implements OperationInterface $currentMessages = array_diff_key($messages, $result->all($domain)); $result->replace($currentMessages, $domain); $result->replace($allIntlMessages + $messages, $intlDomain); + + foreach ($result->getCatalogueMetadata('', $domain) ?? [] as $key => $value) { + if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) { + $result->setCatalogueMetadata($key, $value, $intlDomain); + } + } } } @@ -180,8 +184,6 @@ abstract class AbstractOperation implements OperationInterface * stores the results. * * @param string $domain The domain which the operation will be performed for - * - * @return void */ - abstract protected function processDomain(string $domain); + abstract protected function processDomain(string $domain): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Catalogue/MergeOperation.php b/data/web/inc/lib/vendor/symfony/translation/Catalogue/MergeOperation.php index 1b777a843..e242dbee1 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Catalogue/MergeOperation.php +++ b/data/web/inc/lib/vendor/symfony/translation/Catalogue/MergeOperation.php @@ -24,10 +24,7 @@ use Symfony\Component\Translation\MessageCatalogueInterface; */ class MergeOperation extends AbstractOperation { - /** - * @return void - */ - protected function processDomain(string $domain) + protected function processDomain(string $domain): void { $this->messages[$domain] = [ 'all' => [], diff --git a/data/web/inc/lib/vendor/symfony/translation/Catalogue/TargetOperation.php b/data/web/inc/lib/vendor/symfony/translation/Catalogue/TargetOperation.php index 2c0ec722e..e3e0878bf 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Catalogue/TargetOperation.php +++ b/data/web/inc/lib/vendor/symfony/translation/Catalogue/TargetOperation.php @@ -25,10 +25,7 @@ use Symfony\Component\Translation\MessageCatalogueInterface; */ class TargetOperation extends AbstractOperation { - /** - * @return void - */ - protected function processDomain(string $domain) + protected function processDomain(string $domain): void { $this->messages[$domain] = [ 'all' => [], diff --git a/data/web/inc/lib/vendor/symfony/translation/CatalogueMetadataAwareInterface.php b/data/web/inc/lib/vendor/symfony/translation/CatalogueMetadataAwareInterface.php index c845959f1..19965eae9 100644 --- a/data/web/inc/lib/vendor/symfony/translation/CatalogueMetadataAwareInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation/CatalogueMetadataAwareInterface.php @@ -31,18 +31,14 @@ interface CatalogueMetadataAwareInterface /** * Adds catalogue metadata to a message domain. - * - * @return void */ - public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages'); + public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages'): void; /** * Deletes catalogue metadata for the given key and domain. * * Passing an empty domain will delete all catalogue metadata. Passing an empty key will * delete all metadata for the given domain. - * - * @return void */ - public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages'); + public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages'): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Command/TranslationLintCommand.php b/data/web/inc/lib/vendor/symfony/translation/Command/TranslationLintCommand.php new file mode 100644 index 000000000..e38466ea6 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/translation/Command/TranslationLintCommand.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Exception\ExceptionInterface; +use Symfony\Component\Translation\TranslatorBagInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Lint translations files syntax and outputs encountered errors. + * + * @author Hugo Alliaume + */ +#[AsCommand(name: 'lint:translations', description: 'Lint translations files syntax and outputs encountered errors')] +class TranslationLintCommand extends Command +{ + private SymfonyStyle $io; + + public function __construct( + private TranslatorInterface&TranslatorBagInterface $translator, + private array $enabledLocales = [], + ) { + parent::__construct(); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('locale')) { + $suggestions->suggestValues($this->enabledLocales); + } + } + + protected function configure(): void + { + $this + ->setDefinition([ + new InputOption('locale', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the locales to lint.', $this->enabledLocales), + ]) + ->setHelp(<<<'EOF' + The %command.name% command lint translations. + + php %command.full_name% + EOF + ); + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->io = new SymfonyStyle($input, $output); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $locales = $input->getOption('locale'); + + /** @var array>> $errors */ + $errors = []; + $domainsByLocales = []; + + foreach ($locales as $locale) { + $messageCatalogue = $this->translator->getCatalogue($locale); + + foreach ($domainsByLocales[$locale] = $messageCatalogue->getDomains() as $domain) { + foreach ($messageCatalogue->all($domain) as $id => $translation) { + try { + $this->translator->trans($id, [], $domain, $messageCatalogue->getLocale()); + } catch (ExceptionInterface $e) { + $errors[$locale][$domain][$id] = $e; + } + } + } + } + + if (!$domainsByLocales) { + $this->io->error('No translation files were found.'); + + return Command::SUCCESS; + } + + $this->io->table( + ['Locale', 'Domains', 'Valid?'], + array_map( + static fn (string $locale, array $domains) => [ + $locale, + implode(', ', $domains), + !\array_key_exists($locale, $errors) ? 'Yes' : 'No', + ], + array_keys($domainsByLocales), + $domainsByLocales + ), + ); + + if ($errors) { + foreach ($errors as $locale => $domains) { + foreach ($domains as $domain => $domainsErrors) { + $this->io->section(\sprintf('Errors for locale "%s" and domain "%s"', $locale, $domain)); + + foreach ($domainsErrors as $id => $error) { + $this->io->text(\sprintf('Translation key "%s" is invalid:', $id)); + $this->io->error($error->getMessage()); + } + } + } + + return Command::FAILURE; + } + + $this->io->success('All translations are valid.'); + + return Command::SUCCESS; + } +} diff --git a/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPullCommand.php b/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPullCommand.php index 5d9c092c3..b1a74d74f 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPullCommand.php +++ b/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPullCommand.php @@ -34,22 +34,14 @@ final class TranslationPullCommand extends Command { use TranslationTrait; - private TranslationProviderCollection $providerCollection; - private TranslationWriterInterface $writer; - private TranslationReaderInterface $reader; - private string $defaultLocale; - private array $transPaths; - private array $enabledLocales; - - public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = []) - { - $this->providerCollection = $providerCollection; - $this->writer = $writer; - $this->reader = $reader; - $this->defaultLocale = $defaultLocale; - $this->transPaths = $transPaths; - $this->enabledLocales = $enabledLocales; - + public function __construct( + private TranslationProviderCollection $providerCollection, + private TranslationWriterInterface $writer, + private TranslationReaderInterface $reader, + private string $defaultLocale, + private array $transPaths = [], + private array $enabledLocales = [], + ) { parent::__construct(); } @@ -92,27 +84,28 @@ final class TranslationPullCommand extends Command new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider), new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'), new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'), - new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'), - new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'), - new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'), - new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Write messages as a tree-like structure. Needs --format=yaml. The given value defines the level where to switch to inline YAML'), + new InputOption('domains', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'), + new InputOption('locales', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Override the default output format.', 'xlf12'), + new InputOption('as-tree', null, InputOption::VALUE_REQUIRED, 'Write messages as a tree-like structure. Needs --format=yaml. The given value defines the level where to switch to inline YAML'), ]) ->setHelp(<<<'EOF' -The %command.name% command pulls translations from the given provider. Only -new translations are pulled, existing ones are not overwritten. + The %command.name% command pulls translations from the given provider. Only + new translations are pulled, existing ones are not overwritten. -You can overwrite existing translations (and remove the missing ones on local side) by using the --force flag: + You can overwrite existing translations (and remove the missing ones on local side) by using the --force flag: - php %command.full_name% --force provider + php %command.full_name% --force provider -Full example: + Full example: - php %command.full_name% provider --force --domains=messages --domains=validators --locales=en + php %command.full_name% provider --force --domains=messages --domains=validators --locales=en -This command pulls all translations associated with the messages and validators domains for the en locale. -Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case. -Local translations for others domains and locales are ignored. -EOF + This command pulls all translations associated with the messages and validators domains for the en locale. + Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case. + Local translations for others domains and locales are ignored. + + EOF ) ; } @@ -163,7 +156,7 @@ EOF $this->writer->write($operation->getResult(), $format, $writeOptions); } - $io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + $io->success(\sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); return 0; } @@ -177,7 +170,7 @@ EOF $this->writer->write($catalogue, $format, $writeOptions); } - $io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + $io->success(\sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); return 0; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPushCommand.php b/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPushCommand.php index 1d04adbc9..448aba144 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPushCommand.php +++ b/data/web/inc/lib/vendor/symfony/translation/Command/TranslationPushCommand.php @@ -34,18 +34,12 @@ final class TranslationPushCommand extends Command { use TranslationTrait; - private TranslationProviderCollection $providers; - private TranslationReaderInterface $reader; - private array $transPaths; - private array $enabledLocales; - - public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = []) - { - $this->providers = $providers; - $this->reader = $reader; - $this->transPaths = $transPaths; - $this->enabledLocales = $enabledLocales; - + public function __construct( + private TranslationProviderCollection $providers, + private TranslationReaderInterface $reader, + private array $transPaths = [], + private array $enabledLocales = [], + ) { parent::__construct(); } @@ -60,7 +54,7 @@ final class TranslationPushCommand extends Command if ($input->mustSuggestOptionValuesFor('domains')) { $provider = $this->providers->get($input->getArgument('provider')); - if ($provider && method_exists($provider, 'getDomains')) { + if (method_exists($provider, 'getDomains')) { $domains = $provider->getDomains(); $suggestions->suggestValues($domains); } @@ -83,29 +77,30 @@ final class TranslationPushCommand extends Command new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider), new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'), new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'), - new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'), - new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales), + new InputOption('domains', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'), + new InputOption('locales', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales), ]) ->setHelp(<<<'EOF' -The %command.name% command pushes translations to the given provider. Only new -translations are pushed, existing ones are not overwritten. + The %command.name% command pushes translations to the given provider. Only new + translations are pushed, existing ones are not overwritten. -You can overwrite existing translations by using the --force flag: + You can overwrite existing translations by using the --force flag: - php %command.full_name% --force provider + php %command.full_name% --force provider -You can delete provider translations which are not present locally by using the --delete-missing flag: + You can delete provider translations which are not present locally by using the --delete-missing flag: - php %command.full_name% --delete-missing provider + php %command.full_name% --delete-missing provider -Full example: + Full example: - php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en + php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en -This command pushes all translations associated with the messages and validators domains for the en locale. -Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case. -Provider translations for others domains and locales are ignored. -EOF + This command pushes all translations associated with the messages and validators domains for the en locale. + Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case. + Provider translations for others domains and locales are ignored. + + EOF ) ; } @@ -115,7 +110,7 @@ EOF $provider = $this->providers->get($input->getArgument('provider')); if (!$this->enabledLocales) { - throw new InvalidArgumentException(sprintf('You must define "framework.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME))); + throw new InvalidArgumentException(\sprintf('You must define "framework.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME))); } $io = new SymfonyStyle($input, $output); @@ -139,7 +134,7 @@ EOF if (!$deleteMissing && $force) { $provider->write($localTranslations); - $io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + $io->success(\sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); return 0; } @@ -149,7 +144,7 @@ EOF if ($deleteMissing) { $provider->delete($providerTranslations->diff($localTranslations)); - $io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + $io->success(\sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); // Read provider translations again, after missing translations deletion, // to avoid push freshly deleted translations. @@ -164,7 +159,7 @@ EOF $provider->write($translationsToWrite); - $io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); + $io->success(\sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains))); return 0; } @@ -174,9 +169,9 @@ EOF $domains = []; foreach ($translatorBag->getCatalogues() as $catalogue) { - $domains += $catalogue->getDomains(); + $domains = array_merge($domains, $catalogue->getDomains()); } - return array_unique($domains); + return array_values(array_unique($domains)); } } diff --git a/data/web/inc/lib/vendor/symfony/translation/Command/XliffLintCommand.php b/data/web/inc/lib/vendor/symfony/translation/Command/XliffLintCommand.php index ba946389e..30553821f 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Command/XliffLintCommand.php +++ b/data/web/inc/lib/vendor/symfony/translation/Command/XliffLintCommand.php @@ -39,43 +39,45 @@ class XliffLintCommand extends Command private bool $displayCorrectFiles; private ?\Closure $directoryIteratorProvider; private ?\Closure $isReadableProvider; - private bool $requireStrictFileNames; - public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null, bool $requireStrictFileNames = true) - { + public function __construct( + ?string $name = null, + ?callable $directoryIteratorProvider = null, + ?callable $isReadableProvider = null, + private bool $requireStrictFileNames = true, + ) { parent::__construct($name); $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...); $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); - $this->requireStrictFileNames = $requireStrictFileNames; } - /** - * @return void - */ - protected function configure() + protected function configure(): void { $this ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') - ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) + ->addOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) ->setHelp(<<%command.name% command lints an XLIFF file and outputs to STDOUT -the first encountered syntax error. + The %command.name% command lints an XLIFF file and outputs to STDOUT + the first encountered syntax error. -You can validates XLIFF contents passed from STDIN: + You can validates XLIFF contents passed from STDIN: - cat filename | php %command.full_name% - + cat filename | php %command.full_name% - -You can also validate the syntax of a file: + You can also validate the syntax of a file: - php %command.full_name% filename + php %command.full_name% filename -Or of a whole directory: + Or of a whole directory: - php %command.full_name% dirname - php %command.full_name% dirname --format=json + php %command.full_name% dirname -EOF + The --format option specifies the format of the command output: + + php %command.full_name% dirname --format=json + + EOF ) ; } @@ -98,7 +100,7 @@ EOF $filesInfo = []; foreach ($filenames as $filename) { if (!$this->isReadable($filename)) { - throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + throw new RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename)); } foreach ($this->getFiles($filename) as $file) { @@ -124,18 +126,18 @@ EOF $document->loadXML($content); if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) { - $normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/')); + $normalizedLocalePattern = \sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/')); // strict file names require translation files to be named '____.locale.xlf' // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed // also, the regexp matching must be case-insensitive, as defined for 'target-language' values // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language - $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern); + $expectedFilenamePattern = $this->requireStrictFileNames ? \sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : \sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern); if (0 === preg_match($expectedFilenamePattern, basename($file))) { $errors[] = [ 'line' => -1, 'column' => -1, - 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage), + 'message' => \sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage), ]; } } @@ -151,7 +153,7 @@ EOF libxml_clear_errors(); libxml_use_internal_errors($internal); - return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors]; + return ['file' => $file, 'valid' => !$errors, 'messages' => $errors]; } private function display(SymfonyStyle $io, array $files): int @@ -160,7 +162,7 @@ EOF 'txt' => $this->displayTxt($io, $files), 'json' => $this->displayJson($io, $files), 'github' => $this->displayTxt($io, $files, true), - default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; } @@ -172,25 +174,25 @@ EOF foreach ($filesInfo as $info) { if ($info['valid'] && $this->displayCorrectFiles) { - $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->comment('OK'.($info['file'] ? \sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$erroredFiles; - $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); - $io->listing(array_map(function ($error) use ($info, $githubReporter) { + $io->text(' ERROR '.($info['file'] ? \sprintf(' in %s', $info['file']) : '')); + $io->listing(array_map(static function ($error) use ($info, $githubReporter) { // general document errors have a '-1' line number $line = -1 === $error['line'] ? null : $error['line']; $githubReporter?->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null); - return null === $line ? $error['message'] : sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']); + return null === $line ? $error['message'] : \sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']); }, $info['messages'])); } } if (0 === $erroredFiles) { - $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); + $io->success(\sprintf('All %d XLIFF files contain valid syntax.', $countFiles)); } else { - $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + $io->warning(\sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); } return min($erroredFiles, 1); @@ -200,7 +202,7 @@ EOF { $errors = 0; - array_walk($filesInfo, function (&$v) use (&$errors) { + array_walk($filesInfo, static function (&$v) use (&$errors) { $v['file'] = (string) $v['file']; if (!$v['valid']) { ++$errors; @@ -224,7 +226,7 @@ EOF } foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { - if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) { + if (!\in_array($file->getExtension(), ['xlf', 'xliff'], true)) { continue; } @@ -237,7 +239,7 @@ EOF */ private function getDirectoryIterator(string $directory): iterable { - $default = fn ($directory) => new \RecursiveIteratorIterator( + $default = static fn ($directory) => new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY ); @@ -251,7 +253,7 @@ EOF private function isReadable(string $fileOrDirectory): bool { - $default = fn ($fileOrDirectory) => is_readable($fileOrDirectory); + $default = static fn ($fileOrDirectory) => is_readable($fileOrDirectory); if (null !== $this->isReadableProvider) { return ($this->isReadableProvider)($fileOrDirectory, $default); @@ -278,6 +280,7 @@ EOF } } + /** @return string[] */ private function getAvailableFormatOptions(): array { return ['txt', 'json', 'github']; diff --git a/data/web/inc/lib/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/data/web/inc/lib/vendor/symfony/translation/DataCollector/TranslationDataCollector.php index d4f49cc66..e2e597a70 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DataCollector/TranslationDataCollector.php +++ b/data/web/inc/lib/vendor/symfony/translation/DataCollector/TranslationDataCollector.php @@ -25,11 +25,9 @@ use Symfony\Component\VarDumper\Cloner\Data; */ class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface { - private DataCollectorTranslator $translator; - - public function __construct(DataCollectorTranslator $translator) - { - $this->translator = $translator; + public function __construct( + private DataCollectorTranslator $translator, + ) { } public function lateCollect(): void @@ -46,6 +44,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto { $this->data['locale'] = $this->translator->getLocale(); $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + $this->data['global_parameters'] = $this->translator->getGlobalParameters(); } public function reset(): void @@ -86,6 +85,14 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : []; } + /** + * @internal + */ + public function getGlobalParameters(): Data|array + { + return $this->data['global_parameters'] ?? []; + } + public function getName(): string { return 'translation'; diff --git a/data/web/inc/lib/vendor/symfony/translation/DataCollectorTranslator.php b/data/web/inc/lib/vendor/symfony/translation/DataCollectorTranslator.php index a2832ee8e..801be7493 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DataCollectorTranslator.php +++ b/data/web/inc/lib/vendor/symfony/translation/DataCollectorTranslator.php @@ -12,32 +12,31 @@ namespace Symfony\Component\Translation; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; -use Symfony\Component\Translation\Exception\InvalidArgumentException; +use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** * @author Abdellatif Ait boudad + * + * @final since Symfony 7.1 */ -class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface +class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface, ResetInterface { public const MESSAGE_DEFINED = 0; public const MESSAGE_MISSING = 1; public const MESSAGE_EQUALS_FALLBACK = 2; - private TranslatorInterface $translator; private array $messages = []; - /** - * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator - */ - public function __construct(TranslatorInterface $translator) - { - if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { - throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); - } + public function __construct( + private TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator, + ) { + } - $this->translator = $translator; + public function reset(): void + { + $this->messages = []; } public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string @@ -48,10 +47,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter return $trans; } - /** - * @return void - */ - public function setLocale(string $locale) + public function setLocale(string $locale): void { $this->translator->setLocale($locale); } @@ -74,7 +70,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter public function warmUp(string $cacheDir, ?string $buildDir = null): array { if ($this->translator instanceof WarmableInterface) { - return (array) $this->translator->warmUp($cacheDir, $buildDir); + return $this->translator->warmUp($cacheDir, $buildDir); } return []; @@ -92,10 +88,16 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter return []; } - /** - * @return mixed - */ - public function __call(string $method, array $args) + public function getGlobalParameters(): array + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getGlobalParameters')) { + return $this->translator->getGlobalParameters(); + } + + return []; + } + + public function __call(string $method, array $args): mixed { return $this->translator->{$method}(...$args); } diff --git a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/LoggingTranslatorPass.php b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/LoggingTranslatorPass.php index c21552f97..fba86981c 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/LoggingTranslatorPass.php +++ b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/LoggingTranslatorPass.php @@ -37,7 +37,7 @@ class LoggingTranslatorPass implements CompilerPassInterface $class = $container->getParameterBag()->resolveValue($definition->getClass()); if (!$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias)); + throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias)); } if (!$r->isSubclassOf(TranslatorInterface::class) || !$r->isSubclassOf(TranslatorBagInterface::class)) { diff --git a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php index 2ece6ac7b..0331ef580 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php +++ b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php @@ -20,10 +20,7 @@ use Symfony\Component\DependencyInjection\Reference; */ class TranslationDumperPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('translation.writer')) { return; diff --git a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php index 1baf9341e..4d397fce4 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php +++ b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php @@ -13,7 +13,6 @@ namespace Symfony\Component\Translation\DependencyInjection; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; /** @@ -21,10 +20,7 @@ use Symfony\Component\DependencyInjection\Reference; */ class TranslationExtractorPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('translation.extractor')) { return; @@ -33,11 +29,7 @@ class TranslationExtractorPass implements CompilerPassInterface $definition = $container->getDefinition('translation.extractor'); foreach ($container->findTaggedServiceIds('translation.extractor', true) as $id => $attributes) { - if (!isset($attributes[0]['alias'])) { - throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id)); - } - - $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]); + $definition->addMethodCall('addExtractor', [$attributes[0]['alias'] ?? $id, new Reference($id)]); } } } diff --git a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPass.php index dd6ea3c83..948f378d6 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPass.php +++ b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPass.php @@ -18,10 +18,7 @@ use Symfony\Component\DependencyInjection\Reference; class TranslatorPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('translator.default')) { return; diff --git a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php index 1756e3c8c..0c42e0f3a 100644 --- a/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php +++ b/data/web/inc/lib/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php @@ -42,10 +42,7 @@ class TranslatorPathsPass extends AbstractRecursivePass */ private array $controllers = []; - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('translator')) { return; @@ -96,10 +93,10 @@ class TranslatorPathsPass extends AbstractRecursivePass $class = $this->definitions[$i]->getClass(); if (ServiceLocator::class === $class) { - if (!isset($this->controllers[$this->currentId])) { + if (!isset($this->controllers[$this->currentId ?? ''])) { continue; } - foreach ($this->controllers[$this->currentId] as $class => $_) { + foreach ($this->controllers[$this->currentId ?? ''] as $class => $_) { $this->paths[$class] = true; } } else { diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/CsvFileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/CsvFileDumper.php index 8f5475259..7dfbba497 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/CsvFileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/CsvFileDumper.php @@ -28,7 +28,7 @@ class CsvFileDumper extends FileDumper $handle = fopen('php://memory', 'r+'); foreach ($messages->all($domain) as $source => $target) { - fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure); + fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure, '\\'); } rewind($handle); @@ -40,10 +40,8 @@ class CsvFileDumper extends FileDumper /** * Sets the delimiter and escape character for CSV. - * - * @return void */ - public function setCsvControl(string $delimiter = ';', string $enclosure = '"') + public function setCsvControl(string $delimiter = ';', string $enclosure = '"'): void { $this->delimiter = $delimiter; $this->enclosure = $enclosure; diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/DumperInterface.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/DumperInterface.php index 6bf42931e..c151de020 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/DumperInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/DumperInterface.php @@ -25,8 +25,6 @@ interface DumperInterface * Dumps the message catalogue. * * @param array $options Options that are used by the dumper - * - * @return void */ - public function dump(MessageCatalogue $messages, array $options = []); + public function dump(MessageCatalogue $messages, array $options = []): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/FileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/FileDumper.php index e30d4770e..6fae26438 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/FileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/FileDumper.php @@ -27,25 +27,18 @@ abstract class FileDumper implements DumperInterface { /** * A template for the relative paths to files. - * - * @var string */ - protected $relativePathTemplate = '%domain%.%locale%.%extension%'; + protected string $relativePathTemplate = '%domain%.%locale%.%extension%'; /** * Sets the template for the relative paths to files. - * - * @return void */ - public function setRelativePathTemplate(string $relativePathTemplate) + public function setRelativePathTemplate(string $relativePathTemplate): void { $this->relativePathTemplate = $relativePathTemplate; } - /** - * @return void - */ - public function dump(MessageCatalogue $messages, array $options = []) + public function dump(MessageCatalogue $messages, array $options = []): void { if (!\array_key_exists('path', $options)) { throw new InvalidArgumentException('The file dumper needs a path option.'); @@ -56,8 +49,8 @@ abstract class FileDumper implements DumperInterface $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale()); if (!file_exists($fullpath)) { $directory = \dirname($fullpath); - if (!file_exists($directory) && !@mkdir($directory, 0777, true)) { - throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory)); + if (!file_exists($directory) && !@mkdir($directory, 0o777, true)) { + throw new RuntimeException(\sprintf('Unable to create directory "%s".', $directory)); } } diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/IcuResFileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/IcuResFileDumper.php index 72c1ec089..e603ee8c4 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/IcuResFileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/IcuResFileDumper.php @@ -20,7 +20,7 @@ use Symfony\Component\Translation\MessageCatalogue; */ class IcuResFileDumper extends FileDumper { - protected $relativePathTemplate = '%domain%/%locale%.%extension%'; + protected string $relativePathTemplate = '%domain%/%locale%.%extension%'; public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string { diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/MoFileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/MoFileDumper.php index 9ded5f4ef..a7b4b338c 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/MoFileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/MoFileDumper.php @@ -54,14 +54,11 @@ class MoFileDumper extends FileDumper .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize); } - $output = implode('', array_map($this->writeLong(...), $header)) + return implode('', array_map($this->writeLong(...), $header)) .$sourceOffsets .$targetOffsets .$sources - .$targets - ; - - return $output; + .$targets; } protected function getExtension(): string diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/PoFileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/PoFileDumper.php index a2d0deb78..91e388d5a 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/PoFileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/PoFileDumper.php @@ -30,6 +30,8 @@ class PoFileDumper extends FileDumper $output .= "\n"; $newLine = false; + $isIntlDomain = str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX); + foreach ($messages->all($domain) as $source => $target) { if ($newLine) { $output .= "\n"; @@ -48,17 +50,18 @@ class PoFileDumper extends FileDumper $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':'); } - $sourceRules = $this->getStandardRules($source); - $targetRules = $this->getStandardRules($target); - if (2 == \count($sourceRules) && [] !== $targetRules) { - $output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0])); - $output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1])); + // in an ICU domain the pipe is an ordinary character, pluralization is expressed by the message itself + $sourceRules = $isIntlDomain ? [] : $this->getStandardRules($source); + $targetRules = $isIntlDomain ? [] : $this->getStandardRules($target); + if (2 == \count($sourceRules) && $targetRules) { + $output .= \sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0])); + $output .= \sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1])); foreach ($targetRules as $i => $targetRule) { - $output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule)); + $output .= \sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule)); } } else { - $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); - $output .= sprintf('msgstr "%s"'."\n", $this->escape($target)); + $output .= \sprintf('msgid "%s"'."\n", $this->escape($source)); + $output .= \sprintf('msgstr "%s"'."\n", $this->escape($target)); } } @@ -76,22 +79,22 @@ class PoFileDumper extends FileDumper } $intervalRegexp = <<<'EOF' -/^(?P - ({\s* - (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) - \s*}) + /^(?P + ({\s* + (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*) + \s*}) - | + | - (?P[\[\]]) - \s* - (?P-Inf|\-?\d+(\.\d+)?) - \s*,\s* - (?P\+?Inf|\-?\d+(\.\d+)?) - \s* - (?P[\[\]]) -)\s*(?P.*?)$/xs -EOF; + (?P[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) + )\s*(?P.*?)$/xs + EOF; $standardRules = []; foreach ($parts as $part) { @@ -100,9 +103,9 @@ EOF; if (preg_match($intervalRegexp, $part)) { // Explicit rule is not a standard rule. return []; - } else { - $standardRules[] = $part; } + + $standardRules[] = $part; } return $standardRules; @@ -123,7 +126,7 @@ EOF; $output = null; foreach ((array) $comments as $comment) { - $output .= sprintf('#%s %s'."\n", $prefix, $comment); + $output .= \sprintf('#%s %s'."\n", $prefix, $comment); } return $output; diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/XliffFileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/XliffFileDumper.php index d0f016b23..4ac5c8a3e 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/XliffFileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/XliffFileDumper.php @@ -46,7 +46,7 @@ class XliffFileDumper extends FileDumper return $this->dumpXliff2($defaultLocale, $messages, $domain); } - throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); + throw new InvalidArgumentException(\sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion)); } protected function getExtension(): string @@ -93,7 +93,7 @@ class XliffFileDumper extends FileDumper foreach ($messages->all($domain) as $source => $target) { $translation = $dom->createElement('trans-unit'); - $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')); $translation->setAttribute('resname', $source); $s = $translation->appendChild($dom->createElement('source')); @@ -156,10 +156,11 @@ class XliffFileDumper extends FileDumper } if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) { - $xliff->setAttribute('xmlns:m', 'urn:oasis:names:tc:xliff:metadata:2.0'); - $xliffMetadata = $xliffFile->appendChild($dom->createElement('m:metadata')); + $xliff->setAttribute('xmlns:mda', 'urn:oasis:names:tc:xliff:metadata:2.0'); + $xliffMetadata = $xliffFile->appendChild($dom->createElement('mda:metadata')); + $xliffMetaGroup = $xliffMetadata->appendChild($dom->createElement('mda:metaGroup')); foreach ($catalogueMetadata as $key => $value) { - $xliffMeta = $xliffMetadata->appendChild($dom->createElement('prop')); + $xliffMeta = $xliffMetaGroup->appendChild($dom->createElement('mda:meta')); $xliffMeta->setAttribute('type', $key); $xliffMeta->appendChild($dom->createTextNode($value)); } @@ -167,7 +168,7 @@ class XliffFileDumper extends FileDumper foreach ($messages->all($domain) as $source => $target) { $translation = $dom->createElement('unit'); - $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._')); + $translation->setAttribute('id', strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')); if (\strlen($source) <= 80) { $translation->setAttribute('name', $source); @@ -176,7 +177,7 @@ class XliffFileDumper extends FileDumper $metadata = $messages->getMetadata($source, $domain); // Add notes section - if ($this->hasMetadataArrayInfo('notes', $metadata)) { + if ($this->hasMetadataArrayInfo('notes', $metadata) && $metadata['notes']) { $notesElement = $dom->createElement('notes'); foreach ($metadata['notes'] as $note) { $n = $dom->createElement('note'); @@ -193,6 +194,12 @@ class XliffFileDumper extends FileDumper $segment = $translation->appendChild($dom->createElement('segment')); + if ($this->hasMetadataArrayInfo('segment-attributes', $metadata)) { + foreach ($metadata['segment-attributes'] as $name => $value) { + $segment->setAttribute($name, $value); + } + } + $s = $segment->appendChild($dom->createElement('source')); $s->appendChild($dom->createTextNode($source)); diff --git a/data/web/inc/lib/vendor/symfony/translation/Dumper/YamlFileDumper.php b/data/web/inc/lib/vendor/symfony/translation/Dumper/YamlFileDumper.php index d2670331e..a30eaa319 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Dumper/YamlFileDumper.php +++ b/data/web/inc/lib/vendor/symfony/translation/Dumper/YamlFileDumper.php @@ -23,11 +23,9 @@ use Symfony\Component\Yaml\Yaml; */ class YamlFileDumper extends FileDumper { - private string $extension; - - public function __construct(string $extension = 'yml') - { - $this->extension = $extension; + public function __construct( + private string $extension = 'yml', + ) { } public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string diff --git a/data/web/inc/lib/vendor/symfony/translation/Exception/IncompleteDsnException.php b/data/web/inc/lib/vendor/symfony/translation/Exception/IncompleteDsnException.php index b304bde01..6c9247f89 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Exception/IncompleteDsnException.php +++ b/data/web/inc/lib/vendor/symfony/translation/Exception/IncompleteDsnException.php @@ -16,7 +16,7 @@ class IncompleteDsnException extends InvalidArgumentException public function __construct(string $message, ?string $dsn = null, ?\Throwable $previous = null) { if ($dsn) { - $message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message; + $message = \sprintf('Invalid "%s" provider DSN: ', $dsn).$message; } parent::__construct($message, 0, $previous); diff --git a/data/web/inc/lib/vendor/symfony/translation/Exception/MissingRequiredOptionException.php b/data/web/inc/lib/vendor/symfony/translation/Exception/MissingRequiredOptionException.php index 46152e254..8cef03a81 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Exception/MissingRequiredOptionException.php +++ b/data/web/inc/lib/vendor/symfony/translation/Exception/MissingRequiredOptionException.php @@ -18,7 +18,7 @@ class MissingRequiredOptionException extends IncompleteDsnException { public function __construct(string $option, ?string $dsn = null, ?\Throwable $previous = null) { - $message = sprintf('The option "%s" is required but missing.', $option); + $message = \sprintf('The option "%s" is required but missing.', $option); parent::__construct($message, $dsn, $previous); } diff --git a/data/web/inc/lib/vendor/symfony/translation/Exception/ProviderException.php b/data/web/inc/lib/vendor/symfony/translation/Exception/ProviderException.php index f2981f58b..70e93fc1b 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Exception/ProviderException.php +++ b/data/web/inc/lib/vendor/symfony/translation/Exception/ProviderException.php @@ -18,12 +18,14 @@ use Symfony\Contracts\HttpClient\ResponseInterface; */ class ProviderException extends RuntimeException implements ProviderExceptionInterface { - private ResponseInterface $response; private string $debug; - public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Exception $previous = null) - { - $this->response = $response; + public function __construct( + string $message, + private ResponseInterface $response, + int $code = 0, + ?\Exception $previous = null, + ) { $this->debug = $response->getInfo('debug') ?? ''; parent::__construct($message, $code, $previous); diff --git a/data/web/inc/lib/vendor/symfony/translation/Exception/UnsupportedSchemeException.php b/data/web/inc/lib/vendor/symfony/translation/Exception/UnsupportedSchemeException.php index 8d3295184..ca18444e4 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Exception/UnsupportedSchemeException.php +++ b/data/web/inc/lib/vendor/symfony/translation/Exception/UnsupportedSchemeException.php @@ -43,14 +43,14 @@ class UnsupportedSchemeException extends LogicException } $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null; if ($package && !class_exists($package['class'])) { - parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed. Try running "composer require %s".', $provider, $package['package'])); + parent::__construct(\sprintf('Unable to synchronize translations via "%s" as the provider is not installed. Try running "composer require %s".', $provider, $package['package'])); return; } - $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme()); + $message = \sprintf('The "%s" scheme is not supported', $dsn->getScheme()); if ($name && $supported) { - $message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported)); + $message .= \sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported)); } parent::__construct($message.'.'); diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/AbstractFileExtractor.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/AbstractFileExtractor.php index 4c088b94f..da4a929f0 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/AbstractFileExtractor.php +++ b/data/web/inc/lib/vendor/symfony/translation/Extractor/AbstractFileExtractor.php @@ -49,19 +49,13 @@ abstract class AbstractFileExtractor protected function isFile(string $file): bool { if (!is_file($file)) { - throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file)); + throw new InvalidArgumentException(\sprintf('The "%s" file does not exist.', $file)); } return true; } - /** - * @return bool - */ - abstract protected function canBeExtracted(string $file); + abstract protected function canBeExtracted(string $file): bool; - /** - * @return iterable - */ - abstract protected function extractFromDirectory(string|array $resource); + abstract protected function extractFromDirectory(string|array $resource): iterable; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/ChainExtractor.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/ChainExtractor.php index d36f7f385..ec9982da7 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/ChainExtractor.php +++ b/data/web/inc/lib/vendor/symfony/translation/Extractor/ChainExtractor.php @@ -29,28 +29,20 @@ class ChainExtractor implements ExtractorInterface /** * Adds a loader to the translation extractor. - * - * @return void */ - public function addExtractor(string $format, ExtractorInterface $extractor) + public function addExtractor(string $format, ExtractorInterface $extractor): void { $this->extractors[$format] = $extractor; } - /** - * @return void - */ - public function setPrefix(string $prefix) + public function setPrefix(string $prefix): void { foreach ($this->extractors as $extractor) { $extractor->setPrefix($prefix); } } - /** - * @return void - */ - public function extract(string|iterable $directory, MessageCatalogue $catalogue) + public function extract(string|iterable $directory, MessageCatalogue $catalogue): void { foreach ($this->extractors as $extractor) { $extractor->extract($directory, $catalogue); diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpAstExtractor.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpAstExtractor.php index 06fc77de3..94eef26f9 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpAstExtractor.php +++ b/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpAstExtractor.php @@ -36,7 +36,7 @@ final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorIn private string $prefix = '', ) { if (!class_exists(ParserFactory::class)) { - throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class)); + throw new \LogicException(\sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class)); } $this->parser = (new ParserFactory())->createForHostVersion(); @@ -51,7 +51,6 @@ final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorIn $nameResolver = new NodeVisitor\NameResolver(); $traverser->addVisitor($nameResolver); - /** @var AbstractVisitor&NodeVisitor $visitor */ foreach ($this->visitors as $visitor) { $visitor->initialize($catalogue, $file, $this->prefix); $traverser->addVisitor($visitor); @@ -77,7 +76,7 @@ final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorIn protected function extractFromDirectory(array|string $resource): iterable|Finder { if (!class_exists(Finder::class)) { - throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); + throw new \LogicException(\sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); } return (new Finder())->files()->name('*.php')->in($resource); diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpExtractor.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpExtractor.php deleted file mode 100644 index 7ff27f7c8..000000000 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpExtractor.php +++ /dev/null @@ -1,333 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Translation\Extractor; - -trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated, use "%s" instead.', PhpExtractor::class, PhpAstExtractor::class); - -use Symfony\Component\Finder\Finder; -use Symfony\Component\Translation\MessageCatalogue; - -/** - * PhpExtractor extracts translation messages from a PHP template. - * - * @author Michel Salib - * - * @deprecated since Symfony 6.2, use the PhpAstExtractor instead - */ -class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface -{ - public const MESSAGE_TOKEN = 300; - public const METHOD_ARGUMENTS_TOKEN = 1000; - public const DOMAIN_TOKEN = 1001; - - /** - * Prefix for new found message. - */ - private string $prefix = ''; - - /** - * The sequence that captures translation messages. - */ - protected $sequences = [ - [ - '->', - 'trans', - '(', - self::MESSAGE_TOKEN, - ',', - self::METHOD_ARGUMENTS_TOKEN, - ',', - self::DOMAIN_TOKEN, - ], - [ - '->', - 'trans', - '(', - self::MESSAGE_TOKEN, - ], - [ - 'new', - 'TranslatableMessage', - '(', - self::MESSAGE_TOKEN, - ',', - self::METHOD_ARGUMENTS_TOKEN, - ',', - self::DOMAIN_TOKEN, - ], - [ - 'new', - 'TranslatableMessage', - '(', - self::MESSAGE_TOKEN, - ], - [ - 'new', - '\\', - 'Symfony', - '\\', - 'Component', - '\\', - 'Translation', - '\\', - 'TranslatableMessage', - '(', - self::MESSAGE_TOKEN, - ',', - self::METHOD_ARGUMENTS_TOKEN, - ',', - self::DOMAIN_TOKEN, - ], - [ - 'new', - '\Symfony\Component\Translation\TranslatableMessage', - '(', - self::MESSAGE_TOKEN, - ',', - self::METHOD_ARGUMENTS_TOKEN, - ',', - self::DOMAIN_TOKEN, - ], - [ - 'new', - '\\', - 'Symfony', - '\\', - 'Component', - '\\', - 'Translation', - '\\', - 'TranslatableMessage', - '(', - self::MESSAGE_TOKEN, - ], - [ - 'new', - '\Symfony\Component\Translation\TranslatableMessage', - '(', - self::MESSAGE_TOKEN, - ], - [ - 't', - '(', - self::MESSAGE_TOKEN, - ',', - self::METHOD_ARGUMENTS_TOKEN, - ',', - self::DOMAIN_TOKEN, - ], - [ - 't', - '(', - self::MESSAGE_TOKEN, - ], - ]; - - /** - * @return void - */ - public function extract(string|iterable $resource, MessageCatalogue $catalog) - { - $files = $this->extractFiles($resource); - foreach ($files as $file) { - $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file); - - gc_mem_caches(); - } - } - - /** - * @return void - */ - public function setPrefix(string $prefix) - { - $this->prefix = $prefix; - } - - /** - * Normalizes a token. - */ - protected function normalizeToken(mixed $token): ?string - { - if (isset($token[1]) && 'b"' !== $token) { - return $token[1]; - } - - return $token; - } - - /** - * Seeks to a non-whitespace token. - */ - private function seekToNextRelevantToken(\Iterator $tokenIterator): void - { - for (; $tokenIterator->valid(); $tokenIterator->next()) { - $t = $tokenIterator->current(); - if (\T_WHITESPACE !== $t[0]) { - break; - } - } - } - - private function skipMethodArgument(\Iterator $tokenIterator): void - { - $openBraces = 0; - - for (; $tokenIterator->valid(); $tokenIterator->next()) { - $t = $tokenIterator->current(); - - if ('[' === $t[0] || '(' === $t[0]) { - ++$openBraces; - } - - if (']' === $t[0] || ')' === $t[0]) { - --$openBraces; - } - - if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) { - break; - } - } - } - - /** - * Extracts the message from the iterator while the tokens - * match allowed message tokens. - */ - private function getValue(\Iterator $tokenIterator): string - { - $message = ''; - $docToken = ''; - $docPart = ''; - - for (; $tokenIterator->valid(); $tokenIterator->next()) { - $t = $tokenIterator->current(); - if ('.' === $t) { - // Concatenate with next token - continue; - } - if (!isset($t[1])) { - break; - } - - switch ($t[0]) { - case \T_START_HEREDOC: - $docToken = $t[1]; - break; - case \T_ENCAPSED_AND_WHITESPACE: - case \T_CONSTANT_ENCAPSED_STRING: - if ('' === $docToken) { - $message .= PhpStringTokenParser::parse($t[1]); - } else { - $docPart = $t[1]; - } - break; - case \T_END_HEREDOC: - if ($indentation = strspn($t[1], ' ')) { - $docPartWithLineBreaks = $docPart; - $docPart = ''; - - foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) { - if (\in_array($str, ["\r\n", "\n", "\r"], true)) { - $docPart .= $str; - } else { - $docPart .= substr($str, $indentation); - } - } - } - - $message .= PhpStringTokenParser::parseDocString($docToken, $docPart); - $docToken = ''; - $docPart = ''; - break; - case \T_WHITESPACE: - break; - default: - break 2; - } - } - - return $message; - } - - /** - * Extracts trans message from PHP tokens. - * - * @return void - */ - protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename) - { - $tokenIterator = new \ArrayIterator($tokens); - - for ($key = 0; $key < $tokenIterator->count(); ++$key) { - foreach ($this->sequences as $sequence) { - $message = ''; - $domain = 'messages'; - $tokenIterator->seek($key); - - foreach ($sequence as $sequenceKey => $item) { - $this->seekToNextRelevantToken($tokenIterator); - - if ($this->normalizeToken($tokenIterator->current()) === $item) { - $tokenIterator->next(); - continue; - } elseif (self::MESSAGE_TOKEN === $item) { - $message = $this->getValue($tokenIterator); - - if (\count($sequence) === ($sequenceKey + 1)) { - break; - } - } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) { - $this->skipMethodArgument($tokenIterator); - } elseif (self::DOMAIN_TOKEN === $item) { - $domainToken = $this->getValue($tokenIterator); - if ('' !== $domainToken) { - $domain = $domainToken; - } - - break; - } else { - break; - } - } - - if ($message) { - $catalog->set($message, $this->prefix.$message, $domain); - $metadata = $catalog->getMetadata($message, $domain) ?? []; - $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename); - $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2]; - $catalog->setMetadata($message, $metadata, $domain); - break; - } - } - } - } - - /** - * @throws \InvalidArgumentException - */ - protected function canBeExtracted(string $file): bool - { - return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION); - } - - protected function extractFromDirectory(string|array $directory): iterable - { - if (!class_exists(Finder::class)) { - throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class)); - } - - $finder = new Finder(); - - return $finder->files()->name('*.php')->in($directory); - } -} diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpStringTokenParser.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpStringTokenParser.php deleted file mode 100644 index 2dfc1e387..000000000 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/PhpStringTokenParser.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Translation\Extractor; - -trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated.', PhpStringTokenParser::class); - -/* - * The following is derived from code at http://github.com/nikic/PHP-Parser - * - * Copyright (c) 2011 by Nikita Popov - * - * Some rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * * The names of the contributors may not be used to endorse or - * promote products derived from this software without specific - * prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @deprecated since Symfony 6.2 - */ -class PhpStringTokenParser -{ - protected static $replacements = [ - '\\' => '\\', - '$' => '$', - 'n' => "\n", - 'r' => "\r", - 't' => "\t", - 'f' => "\f", - 'v' => "\v", - 'e' => "\x1B", - ]; - - /** - * Parses a string token. - * - * @param string $str String token content - */ - public static function parse(string $str): string - { - $bLength = 0; - if ('b' === $str[0]) { - $bLength = 1; - } - - if ('\'' === $str[$bLength]) { - return str_replace( - ['\\\\', '\\\''], - ['\\', '\''], - substr($str, $bLength + 1, -1) - ); - } else { - return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"'); - } - } - - /** - * Parses escape sequences in strings (all string types apart from single quoted). - * - * @param string $str String without quotes - * @param string|null $quote Quote type - */ - public static function parseEscapeSequences(string $str, ?string $quote = null): string - { - if (null !== $quote) { - $str = str_replace('\\'.$quote, $quote, $str); - } - - return preg_replace_callback( - '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~', - [__CLASS__, 'parseCallback'], - $str - ); - } - - private static function parseCallback(array $matches): string - { - $str = $matches[1]; - - if (isset(self::$replacements[$str])) { - return self::$replacements[$str]; - } elseif ('x' === $str[0] || 'X' === $str[0]) { - return \chr(hexdec($str)); - } else { - return \chr(octdec($str)); - } - } - - /** - * Parses a constant doc string. - * - * @param string $startToken Doc string start token content (<<value; - /** @var Node\Expr\ArrayItem $item */ foreach ($options->items as $item) { if (!$item->key instanceof Node\Scalar\String_) { continue; diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php index 53a6981a2..a3dcd6d2b 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php +++ b/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php @@ -39,12 +39,12 @@ final class TransMethodVisitor extends AbstractVisitor implements NodeVisitor return null; } - $name = (string) $node->name; + $name = $node->name instanceof Node\Name ? $node->name->getLast() : (string) $node->name; if ('trans' === $name || 't' === $name) { $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node); - if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) { + if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'id')) { return null; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php b/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php index 6bd8bb022..28e63a2c2 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php +++ b/data/web/inc/lib/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Translation\Extractor\Visitor; use PhpParser\Node; use PhpParser\NodeVisitor; +use Symfony\Component\Translation\TranslatableMessage; /** * @author Mathieu Santostefano @@ -39,7 +40,9 @@ final class TranslatableMessageVisitor extends AbstractVisitor implements NodeVi return null; } - if (!\in_array('TranslatableMessage', $className->getParts(), true)) { + // the name resolver gives a fully qualified name; templates without a "use" + // statement resolve to the global namespace, which is accepted as well + if (!\in_array($className->toString(), [TranslatableMessage::class, 'TranslatableMessage'], true)) { return null; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Formatter/IntlFormatter.php b/data/web/inc/lib/vendor/symfony/translation/Formatter/IntlFormatter.php index e62de253f..87cb00733 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Formatter/IntlFormatter.php +++ b/data/web/inc/lib/vendor/symfony/translation/Formatter/IntlFormatter.php @@ -37,7 +37,7 @@ class IntlFormatter implements IntlFormatterInterface try { $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message); } catch (\IntlException $e) { - throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); + throw new InvalidArgumentException(\sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e); } } @@ -49,7 +49,7 @@ class IntlFormatter implements IntlFormatterInterface } if (false === $message = $formatter->format($parameters)) { - throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); + throw new InvalidArgumentException(\sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage()); } return $message; diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/CsvFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/CsvFileLoader.php index 7f2f96be6..dbf8ea798 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/CsvFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/CsvFileLoader.php @@ -22,29 +22,28 @@ class CsvFileLoader extends FileLoader { private string $delimiter = ';'; private string $enclosure = '"'; - private string $escape = '\\'; + /** + * @deprecated since Symfony 7.2, to be removed in 8.0 + */ + private string $escape = ''; protected function loadResource(string $resource): array { $messages = []; - try { - $file = new \SplFileObject($resource, 'rb'); - } catch (\RuntimeException $e) { - throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e); + if (!$file = @fopen($resource, 'r')) { + throw new NotFoundResourceException(\sprintf('Error opening file "%s".', $resource)); } - $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); - $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); - - foreach ($file as $data) { - if (false === $data) { - continue; - } - - if (!str_starts_with($data[0], '#') && isset($data[1]) && 2 === \count($data)) { - $messages[$data[0]] = $data[1]; + try { + while (false !== $data = fgetcsv($file, null, $this->delimiter, $this->enclosure, $this->escape)) { + // empty lines are read as [null] + if (isset($data[1]) && 2 === \count($data) && !str_starts_with($data[0], '#')) { + $messages[$data[0]] = $data[1]; + } } + } finally { + fclose($file); } return $messages; @@ -52,13 +51,15 @@ class CsvFileLoader extends FileLoader /** * Sets the delimiter, enclosure, and escape character for CSV. - * - * @return void */ - public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\') + public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = ''): void { $this->delimiter = $delimiter; $this->enclosure = $enclosure; + if ('' !== $escape) { + trigger_deprecation('symfony/translation', '7.2', 'The "escape" parameter of the "%s" method is deprecated. It will be removed in 8.0.', __METHOD__); + } + $this->escape = $escape; } } diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/FileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/FileLoader.php index 877c3bbc7..94f6e2022 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/FileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/FileLoader.php @@ -24,11 +24,11 @@ abstract class FileLoader extends ArrayLoader public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue { if (!stream_is_local($resource)) { - throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); } if (!file_exists($resource)) { - throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); } $messages = $this->loadResource($resource); @@ -38,7 +38,7 @@ abstract class FileLoader extends ArrayLoader // not an array if (!\is_array($messages)) { - throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('Unable to load file "%s".', $resource)); } $catalogue = parent::load($messages, $locale, $domain); diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/IcuDatFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/IcuDatFileLoader.php index 76e4e7f02..1af864303 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/IcuDatFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/IcuDatFileLoader.php @@ -26,11 +26,11 @@ class IcuDatFileLoader extends IcuResFileLoader public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue { if (!stream_is_local($resource.'.dat')) { - throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); } if (!file_exists($resource.'.dat')) { - throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); } try { @@ -40,7 +40,7 @@ class IcuDatFileLoader extends IcuResFileLoader } if (!$rb) { - throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + throw new InvalidResourceException(\sprintf('Cannot load resource "%s".', $resource)); } elseif (intl_is_failure($rb->getErrorCode())) { throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); } diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/IcuResFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/IcuResFileLoader.php index 949dd9792..8ada43dca 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/IcuResFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/IcuResFileLoader.php @@ -26,11 +26,11 @@ class IcuResFileLoader implements LoaderInterface public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue { if (!stream_is_local($resource)) { - throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); } if (!is_dir($resource)) { - throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); } try { @@ -40,7 +40,7 @@ class IcuResFileLoader implements LoaderInterface } if (!$rb) { - throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource)); + throw new InvalidResourceException(\sprintf('Cannot load resource "%s".', $resource)); } elseif (intl_is_failure($rb->getErrorCode())) { throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); } diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/PoFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/PoFileLoader.php index 620d97339..750209154 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/PoFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/PoFileLoader.php @@ -55,6 +55,7 @@ class PoFileLoader extends FileLoader * - No support for comments spanning multiple lines. * - Translator and extracted comments are treated as being the same type. * - Message IDs are allowed to have other encodings as just US-ASCII. + * - Contexts (msgctxt) are parsed but discarded. * * Items with an empty id are ignored. */ @@ -65,6 +66,7 @@ class PoFileLoader extends FileLoader $defaults = [ 'ids' => [], 'translated' => null, + 'context' => null, ]; $messages = []; @@ -76,23 +78,28 @@ class PoFileLoader extends FileLoader if ('' === $line) { // Whitespace indicated current item is done - if (!\in_array('fuzzy', $flags)) { - $this->addMessage($messages, $item); - } - $item = $defaults; - $flags = []; + $this->saveItem($messages, $item, $flags, $defaults); } elseif (str_starts_with($line, '#,')) { + // flags belong to the next entry, so the previous one ends here + if (null !== $item['translated']) { + $this->saveItem($messages, $item, $flags, $defaults); + } $flags = array_map('trim', explode(',', substr($line, 2))); + } elseif (str_starts_with($line, 'msgctxt "')) { + if (null !== $item['translated']) { + $this->saveItem($messages, $item, $flags, $defaults); + } + $item['context'] = substr($line, 9, -1); } elseif (str_starts_with($line, 'msgid "')) { // We start a new msg so save previous - // TODO: this fails when comments or contexts are added - $this->addMessage($messages, $item); - $item = $defaults; + if ($item['ids']) { + $this->saveItem($messages, $item, $flags, $defaults); + } $item['ids']['singular'] = substr($line, 7, -1); } elseif (str_starts_with($line, 'msgstr "')) { $item['translated'] = substr($line, 8, -1); } elseif ('"' === $line[0]) { - $continues = isset($item['translated']) ? 'translated' : 'ids'; + $continues = isset($item['translated']) ? 'translated' : ($item['ids'] ? 'ids' : 'context'); if (\is_array($item[$continues])) { end($item[$continues]); @@ -108,14 +115,21 @@ class PoFileLoader extends FileLoader } } // save last item - if (!\in_array('fuzzy', $flags)) { - $this->addMessage($messages, $item); - } + $this->saveItem($messages, $item, $flags, $defaults); fclose($stream); return $messages; } + private function saveItem(array &$messages, array &$item, array &$flags, array $defaults): void + { + if (!\in_array('fuzzy', $flags, true)) { + $this->addMessage($messages, $item); + } + $item = $defaults; + $flags = []; + } + /** * Save a translation item to the messages. * diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/QtFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/QtFileLoader.php index 235f85ee9..c9554bf55 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/QtFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/QtFileLoader.php @@ -32,17 +32,17 @@ class QtFileLoader implements LoaderInterface } if (!stream_is_local($resource)) { - throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); } if (!file_exists($resource)) { - throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); } try { $dom = XmlUtils::loadFile($resource); } catch (\InvalidArgumentException $e) { - throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e); + throw new InvalidResourceException(\sprintf('Unable to load "%s".', $resource), $e->getCode(), $e); } $internalErrors = libxml_use_internal_errors(true); @@ -57,7 +57,7 @@ class QtFileLoader implements LoaderInterface foreach ($translations as $translation) { $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue; - if (!empty($translationValue)) { + if ($translationValue) { $catalogue->set( (string) $translation->getElementsByTagName('source')->item(0)->nodeValue, $translationValue, diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/XliffFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/XliffFileLoader.php index 31b3251ba..784767dee 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/XliffFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/XliffFileLoader.php @@ -36,15 +36,15 @@ class XliffFileLoader implements LoaderInterface if (!$this->isXmlString($resource)) { if (!stream_is_local($resource)) { - throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('This is not a local file "%s".', $resource)); } if (!file_exists($resource)) { - throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + throw new NotFoundResourceException(\sprintf('File "%s" not found.', $resource)); } if (!is_file($resource)) { - throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); + throw new InvalidResourceException(\sprintf('This is neither a file nor an XLIFF string "%s".', $resource)); } } @@ -55,11 +55,11 @@ class XliffFileLoader implements LoaderInterface $dom = XmlUtils::loadFile($resource); } } catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) { - throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); + throw new InvalidResourceException(\sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e); } if ($errors = XliffUtils::validateSchema($dom)) { - throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); + throw new InvalidResourceException(\sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors)); } $catalogue = new MessageCatalogue($locale); @@ -112,16 +112,20 @@ class XliffFileLoader implements LoaderInterface continue; } - if (isset($translation->target) && 'needs-translation' === (string) $translation->target->attributes()['state']) { + $source = (string) (isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source); + + if (isset($translation->target) + && 'needs-translation' === (string) $translation->target->attributes()['state'] + && \in_array((string) $translation->target, [$source, (string) $translation->source], true) + ) { continue; } - $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; // If the xlf file has another encoding specified, try to convert it because // simple_xml will always return utf-8 encoded values $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding); - $catalogue->set((string) $source, $target, $domain); + $catalogue->set($source, $target, $domain); $metadata = [ 'source' => (string) $translation->source, @@ -144,7 +148,7 @@ class XliffFileLoader implements LoaderInterface $metadata['id'] = (string) $attributes['id']; } - $catalogue->setMetadata((string) $source, $metadata, $domain); + $catalogue->setMetadata($source, $metadata, $domain); } } } @@ -154,6 +158,12 @@ class XliffFileLoader implements LoaderInterface $xml = simplexml_import_dom($dom); $encoding = $dom->encoding ? strtoupper($dom->encoding) : null; + $xml->registerXPathNamespace('mda', 'urn:oasis:names:tc:xliff:metadata:2.0'); + + foreach ($xml->xpath('//mda:meta') as $meta) { + $catalogue->setCatalogueMetadata($meta->attributes()['type'] ?? '', (string) $meta, $domain); + } + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); foreach ($xml->xpath('//xliff:unit') as $unit) { @@ -168,6 +178,13 @@ class XliffFileLoader implements LoaderInterface $catalogue->set((string) $source, $target, $domain); $metadata = []; + if ($segment->attributes()) { + $metadata['segment-attributes'] = []; + foreach ($segment->attributes() as $key => $value) { + $metadata['segment-attributes'][$key] = (string) $value; + } + } + if (isset($segment->target) && $segment->target->attributes()) { $metadata['target-attributes'] = []; foreach ($segment->target->attributes() as $key => $value) { @@ -197,7 +214,7 @@ class XliffFileLoader implements LoaderInterface */ private function utf8ToCharset(string $content, ?string $encoding = null): string { - if ('UTF-8' !== $encoding && !empty($encoding)) { + if ('UTF-8' !== $encoding && $encoding) { return mb_convert_encoding($content, $encoding, 'UTF-8'); } diff --git a/data/web/inc/lib/vendor/symfony/translation/Loader/YamlFileLoader.php b/data/web/inc/lib/vendor/symfony/translation/Loader/YamlFileLoader.php index 48e735d16..39d5a0e99 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Loader/YamlFileLoader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Loader/YamlFileLoader.php @@ -29,7 +29,7 @@ class YamlFileLoader extends FileLoader protected function loadResource(string $resource): array { if (!isset($this->yamlParser)) { - if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { + if (!class_exists(YamlParser::class)) { throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.'); } @@ -39,11 +39,11 @@ class YamlFileLoader extends FileLoader try { $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT); } catch (ParseException $e) { - throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e); + throw new InvalidResourceException(\sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e); } if (null !== $messages && !\is_array($messages)) { - throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource)); + throw new InvalidResourceException(\sprintf('Unable to load file "%s".', $resource)); } return $messages ?: []; diff --git a/data/web/inc/lib/vendor/symfony/translation/LocaleSwitcher.php b/data/web/inc/lib/vendor/symfony/translation/LocaleSwitcher.php index c07809c58..4950a56bb 100644 --- a/data/web/inc/lib/vendor/symfony/translation/LocaleSwitcher.php +++ b/data/web/inc/lib/vendor/symfony/translation/LocaleSwitcher.php @@ -34,9 +34,14 @@ class LocaleSwitcher implements LocaleAwareInterface public function setLocale(string $locale): void { - if (class_exists(\Locale::class)) { - \Locale::setDefault($locale); + // Silently ignore if the intl extension is not loaded + try { + if (class_exists(\Locale::class, false)) { + \Locale::setDefault($locale); + } + } catch (\Exception) { } + $this->locale = $locale; $this->requestContext?->setParameter('_locale', $locale); diff --git a/data/web/inc/lib/vendor/symfony/translation/LoggingTranslator.php b/data/web/inc/lib/vendor/symfony/translation/LoggingTranslator.php index 4a560bd6a..84020d8a0 100644 --- a/data/web/inc/lib/vendor/symfony/translation/LoggingTranslator.php +++ b/data/web/inc/lib/vendor/symfony/translation/LoggingTranslator.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Translation; use Psr\Log\LoggerInterface; -use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -21,20 +20,10 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface { - private TranslatorInterface $translator; - private LoggerInterface $logger; - - /** - * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface - */ - public function __construct(TranslatorInterface $translator, LoggerInterface $logger) - { - if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) { - throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator))); - } - - $this->translator = $translator; - $this->logger = $logger; + public function __construct( + private TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator, + private LoggerInterface $logger, + ) { } public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string @@ -45,10 +34,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, return $trans; } - /** - * @return void - */ - public function setLocale(string $locale) + public function setLocale(string $locale): void { $prev = $this->translator->getLocale(); $this->translator->setLocale($locale); @@ -56,7 +42,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, return; } - $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); + $this->logger->debug(\sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale)); } public function getLocale(): string @@ -86,10 +72,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, return []; } - /** - * @return mixed - */ - public function __call(string $method, array $args) + public function __call(string $method, array $args): mixed { return $this->translator->{$method}(...$args); } diff --git a/data/web/inc/lib/vendor/symfony/translation/MessageCatalogue.php b/data/web/inc/lib/vendor/symfony/translation/MessageCatalogue.php index d56f04393..eac50bb1f 100644 --- a/data/web/inc/lib/vendor/symfony/translation/MessageCatalogue.php +++ b/data/web/inc/lib/vendor/symfony/translation/MessageCatalogue.php @@ -19,21 +19,19 @@ use Symfony\Component\Translation\Exception\LogicException; */ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface, CatalogueMetadataAwareInterface { - private array $messages = []; private array $metadata = []; private array $catalogueMetadata = []; private array $resources = []; - private string $locale; private ?MessageCatalogueInterface $fallbackCatalogue = null; private ?self $parent = null; /** * @param array $messages An array of messages classified by domain */ - public function __construct(string $locale, array $messages = []) - { - $this->locale = $locale; - $this->messages = $messages; + public function __construct( + private string $locale, + private array $messages = [], + ) { } public function getLocale(): string @@ -80,10 +78,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf return $allMessages; } - /** - * @return void - */ - public function set(string $id, string $translation, string $domain = 'messages') + public function set(string $id, string $translation, string $domain = 'messages'): void { $this->add([$id => $translation], $domain); } @@ -123,20 +118,14 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf return $id; } - /** - * @return void - */ - public function replace(array $messages, string $domain = 'messages') + public function replace(array $messages, string $domain = 'messages'): void { unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]); $this->add($messages, $domain); } - /** - * @return void - */ - public function add(array $messages, string $domain = 'messages') + public function add(array $messages, string $domain = 'messages'): void { $altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX; foreach ($messages as $id => $message) { @@ -149,13 +138,10 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf } } - /** - * @return void - */ - public function addCatalogue(MessageCatalogueInterface $catalogue) + public function addCatalogue(MessageCatalogueInterface $catalogue): void { if ($catalogue->getLocale() !== $this->locale) { - throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); + throw new LogicException(\sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); } foreach ($catalogue->all() as $domain => $messages) { @@ -181,23 +167,20 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf } } - /** - * @return void - */ - public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) + public function addFallbackCatalogue(MessageCatalogueInterface $catalogue): void { // detect circular references $c = $catalogue; while ($c = $c->getFallbackCatalogue()) { if ($c->getLocale() === $this->getLocale()) { - throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + throw new LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); } } $c = $this; do { if ($c->getLocale() === $catalogue->getLocale()) { - throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + throw new LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); } foreach ($catalogue->getResources() as $resource) { @@ -223,10 +206,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf return array_values($this->resources); } - /** - * @return void - */ - public function addResource(ResourceInterface $resource) + public function addResource(ResourceInterface $resource): void { $this->resources[$resource->__toString()] = $resource; } @@ -237,6 +217,16 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf return $this->metadata; } + if (isset($this->metadata[$domain.self::INTL_DOMAIN_SUFFIX])) { + if ('' === $key) { + return $this->metadata[$domain.self::INTL_DOMAIN_SUFFIX]; + } + + if (isset($this->metadata[$domain.self::INTL_DOMAIN_SUFFIX][$key])) { + return $this->metadata[$domain.self::INTL_DOMAIN_SUFFIX][$key]; + } + } + if (isset($this->metadata[$domain])) { if ('' == $key) { return $this->metadata[$domain]; @@ -250,18 +240,12 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf return null; } - /** - * @return void - */ - public function setMetadata(string $key, mixed $value, string $domain = 'messages') + public function setMetadata(string $key, mixed $value, string $domain = 'messages'): void { $this->metadata[$domain][$key] = $value; } - /** - * @return void - */ - public function deleteMetadata(string $key = '', string $domain = 'messages') + public function deleteMetadata(string $key = '', string $domain = 'messages'): void { if ('' == $domain) { $this->metadata = []; @@ -291,18 +275,12 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf return null; } - /** - * @return void - */ - public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages') + public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages'): void { $this->catalogueMetadata[$domain][$key] = $value; } - /** - * @return void - */ - public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages') + public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages'): void { if (!$domain) { $this->catalogueMetadata = []; diff --git a/data/web/inc/lib/vendor/symfony/translation/MessageCatalogueInterface.php b/data/web/inc/lib/vendor/symfony/translation/MessageCatalogueInterface.php index fd0d26d7e..5d6356053 100644 --- a/data/web/inc/lib/vendor/symfony/translation/MessageCatalogueInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation/MessageCatalogueInterface.php @@ -45,10 +45,8 @@ interface MessageCatalogueInterface * @param string $id The message id * @param string $translation The messages translation * @param string $domain The domain name - * - * @return void */ - public function set(string $id, string $translation, string $domain = 'messages'); + public function set(string $id, string $translation, string $domain = 'messages'): void; /** * Checks if a message has a translation. @@ -79,39 +77,31 @@ interface MessageCatalogueInterface * * @param array $messages An array of translations * @param string $domain The domain name - * - * @return void */ - public function replace(array $messages, string $domain = 'messages'); + public function replace(array $messages, string $domain = 'messages'): void; /** * Adds translations for a given domain. * * @param array $messages An array of translations * @param string $domain The domain name - * - * @return void */ - public function add(array $messages, string $domain = 'messages'); + public function add(array $messages, string $domain = 'messages'): void; /** * Merges translations from the given Catalogue into the current one. * * The two catalogues must have the same locale. - * - * @return void */ - public function addCatalogue(self $catalogue); + public function addCatalogue(self $catalogue): void; /** * Merges translations from the given Catalogue into the current one * only when the translation does not exist. * * This is used to provide default translations when they do not exist for the current locale. - * - * @return void */ - public function addFallbackCatalogue(self $catalogue); + public function addFallbackCatalogue(self $catalogue): void; /** * Gets the fallback catalogue. @@ -127,8 +117,6 @@ interface MessageCatalogueInterface /** * Adds a resource for this collection. - * - * @return void */ - public function addResource(ResourceInterface $resource); + public function addResource(ResourceInterface $resource): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/MetadataAwareInterface.php b/data/web/inc/lib/vendor/symfony/translation/MetadataAwareInterface.php index 39e5326c3..12e4f33ba 100644 --- a/data/web/inc/lib/vendor/symfony/translation/MetadataAwareInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation/MetadataAwareInterface.php @@ -31,18 +31,14 @@ interface MetadataAwareInterface /** * Adds metadata to a message domain. - * - * @return void */ - public function setMetadata(string $key, mixed $value, string $domain = 'messages'); + public function setMetadata(string $key, mixed $value, string $domain = 'messages'): void; /** * Deletes metadata for the given key and domain. * * Passing an empty domain will delete all metadata. Passing an empty key will * delete all metadata for the given domain. - * - * @return void */ - public function deleteMetadata(string $key = '', string $domain = 'messages'); + public function deleteMetadata(string $key = '', string $domain = 'messages'): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Provider/FilteringProvider.php b/data/web/inc/lib/vendor/symfony/translation/Provider/FilteringProvider.php index d4465b9fd..cc11dc3d1 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Provider/FilteringProvider.php +++ b/data/web/inc/lib/vendor/symfony/translation/Provider/FilteringProvider.php @@ -21,15 +21,11 @@ use Symfony\Component\Translation\TranslatorBagInterface; */ class FilteringProvider implements ProviderInterface { - private ProviderInterface $provider; - private array $locales; - private array $domains; - - public function __construct(ProviderInterface $provider, array $locales, array $domains = []) - { - $this->provider = $provider; - $this->locales = $locales; - $this->domains = $domains; + public function __construct( + private ProviderInterface $provider, + private array $locales, + private array $domains = [], + ) { } public function __toString(): string diff --git a/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollection.php b/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollection.php index b917415ba..878998fec 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollection.php +++ b/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollection.php @@ -44,7 +44,7 @@ final class TranslationProviderCollection public function get(string $name): ProviderInterface { if (!$this->has($name)) { - throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this)); + throw new InvalidArgumentException(\sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this)); } return $this->providers[$name]; diff --git a/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php b/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php index 6300c8750..2c8c55159 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php +++ b/data/web/inc/lib/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php @@ -18,16 +18,13 @@ use Symfony\Component\Translation\Exception\UnsupportedSchemeException; */ class TranslationProviderCollectionFactory { - private iterable $factories; - private array $enabledLocales; - /** * @param iterable $factories */ - public function __construct(iterable $factories, array $enabledLocales) - { - $this->factories = $factories; - $this->enabledLocales = $enabledLocales; + public function __construct( + private iterable $factories, + private array $enabledLocales, + ) { } public function fromConfig(array $config): TranslationProviderCollection diff --git a/data/web/inc/lib/vendor/symfony/translation/PseudoLocalizationTranslator.php b/data/web/inc/lib/vendor/symfony/translation/PseudoLocalizationTranslator.php index f26909f5e..147c2cefb 100644 --- a/data/web/inc/lib/vendor/symfony/translation/PseudoLocalizationTranslator.php +++ b/data/web/inc/lib/vendor/symfony/translation/PseudoLocalizationTranslator.php @@ -11,16 +11,16 @@ namespace Symfony\Component\Translation; +use Symfony\Component\Translation\Exception\LogicException; use Symfony\Contracts\Translation\TranslatorInterface; /** * This translator should only be used in a development environment. */ -final class PseudoLocalizationTranslator implements TranslatorInterface +final class PseudoLocalizationTranslator implements TranslatorInterface, TranslatorBagInterface { private const EXPANSION_CHARACTER = '~'; - private TranslatorInterface $translator; private bool $accents; private float $expansionFactor; private bool $brackets; @@ -55,7 +55,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface * * parse_html: * type: boolean * default: false - * description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML + * description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand or when it contains HTML * warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo
bar" => "foo
bar
" * * * localizable_html_attributes: @@ -64,8 +64,10 @@ final class PseudoLocalizationTranslator implements TranslatorInterface * description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true * example: if ["title"], and with the "accents" option set to true, "Profile" => "Þŕöƒîļé" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged. */ - public function __construct(TranslatorInterface $translator, array $options = []) - { + public function __construct( + private TranslatorInterface $translator, + array $options = [], + ) { $this->translator = $translator; $this->accents = $options['accents'] ?? true; @@ -114,6 +116,24 @@ final class PseudoLocalizationTranslator implements TranslatorInterface return $this->translator->getLocale(); } + public function getCatalogue(?string $locale = null): MessageCatalogueInterface + { + if (!$this->translator instanceof TranslatorBagInterface) { + throw new LogicException(\sprintf('The "%s()" method cannot be called as the wrapped translator class "%s" does not implement the "%s".', __METHOD__, $this->translator::class, TranslatorBagInterface::class)); + } + + return $this->translator->getCatalogue($locale); + } + + public function getCatalogues(): array + { + if (!$this->translator instanceof TranslatorBagInterface) { + throw new LogicException(\sprintf('The "%s()" method cannot be called as the wrapped translator class "%s" does not implement the "%s".', __METHOD__, $this->translator::class, TranslatorBagInterface::class)); + } + + return $this->translator->getCatalogues(); + } + private function getParts(string $originalTrans): array { if (!$this->parseHTML) { @@ -146,7 +166,6 @@ final class PseudoLocalizationTranslator implements TranslatorInterface $parts[] = [false, false, '<'.$childNode->tagName]; - /** @var \DOMAttr $attribute */ foreach ($childNode->attributes as $attribute) { $parts[] = [false, false, ' '.$attribute->nodeName.'="']; @@ -164,7 +183,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface $parts[] = [false, false, '>']; - $parts = array_merge($parts, $this->parseNode($childNode, $parts)); + $parts = array_merge($parts, $this->parseNode($childNode)); $parts[] = [false, false, 'tagName.'>']; } @@ -363,3 +382,5 @@ final class PseudoLocalizationTranslator implements TranslatorInterface return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding); } } + +// @php-cs-fixer-ignore random_api_migration As logic is coupled with mt_srand() in tests diff --git a/data/web/inc/lib/vendor/symfony/translation/README.md b/data/web/inc/lib/vendor/symfony/translation/README.md index 32e4017b7..1cfea8fb3 100644 --- a/data/web/inc/lib/vendor/symfony/translation/README.md +++ b/data/web/inc/lib/vendor/symfony/translation/README.md @@ -6,8 +6,8 @@ The Translation component provides tools to internationalize your application. Getting Started --------------- -``` -$ composer require symfony/translation +```bash +composer require symfony/translation ``` ```php @@ -26,7 +26,7 @@ echo $translator->trans('Hello World!'); // outputs « Bonjour ! » Sponsor ------- -Help Symfony by [sponsoring][1] its development! +Help Symfony by [sponsoring][3] its development! Resources --------- @@ -37,4 +37,4 @@ Resources [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) -[1]: https://symfony.com/sponsor +[3]: https://symfony.com/sponsor diff --git a/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReader.php b/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReader.php index 01408d4dc..928e2c562 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReader.php +++ b/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReader.php @@ -33,18 +33,13 @@ class TranslationReader implements TranslationReaderInterface * Adds a loader to the translation extractor. * * @param string $format The format of the loader - * - * @return void */ - public function addLoader(string $format, LoaderInterface $loader) + public function addLoader(string $format, LoaderInterface $loader): void { $this->loaders[$format] = $loader; } - /** - * @return void - */ - public function read(string $directory, MessageCatalogue $catalogue) + public function read(string $directory, MessageCatalogue $catalogue): void { if (!is_dir($directory)) { return; diff --git a/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReaderInterface.php b/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReaderInterface.php index ea74dc23f..bab6e59be 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReaderInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation/Reader/TranslationReaderInterface.php @@ -22,8 +22,6 @@ interface TranslationReaderInterface { /** * Reads translation messages from a directory to the catalogue. - * - * @return void */ - public function read(string $directory, MessageCatalogue $catalogue); + public function read(string $directory, MessageCatalogue $catalogue): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/Resources/bin/translation-status.php b/data/web/inc/lib/vendor/symfony/translation/Resources/bin/translation-status.php index 8064190d8..c038bfe09 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Resources/bin/translation-status.php +++ b/data/web/inc/lib/vendor/symfony/translation/Resources/bin/translation-status.php @@ -15,27 +15,27 @@ if ('cli' !== \PHP_SAPI) { $usageInstructions = << count($translation['missingKeys']), array_values($translationStatus))); - $totalTranslationMismatches += array_sum(array_map(fn ($translation) => count($translation['mismatches']), array_values($translationStatus))); + $totalMissingTranslations += array_sum(array_map(static fn ($translation) => count($translation['missingKeys']), array_values($translationStatus))); + $totalTranslationMismatches += array_sum(array_map(static fn ($translation) => count($translation['mismatches']), array_values($translationStatus))); printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']); } @@ -162,11 +162,11 @@ function extractLocaleFromFilePath($filePath) function extractTranslationKeys($filePath): array { $translationKeys = []; - $contents = new \SimpleXMLElement(file_get_contents($filePath)); + $contents = new SimpleXMLElement(file_get_contents($filePath)); foreach ($contents->file->body->{'trans-unit'} as $translationKey) { $translationId = (string) $translationKey['id']; - $translationKey = (string) $translationKey->source; + $translationKey = (string) ($translationKey['resname'] ?? $translationKey->source); $translationKeys[$translationId] = $translationKey; } @@ -218,7 +218,7 @@ function printTable($translations, $verboseOutput, bool $includeCompletedLanguag if ($translation['translated'] > $translation['total']) { textColorRed(); - } elseif (count($translation['mismatches']) > 0) { + } elseif ($translation['mismatches']) { textColorRed(); } elseif ($translation['is_completed']) { textColorGreen(); @@ -235,7 +235,7 @@ function printTable($translations, $verboseOutput, bool $includeCompletedLanguag textColorNormal(); $shouldBeClosed = false; - if (true === $verboseOutput && count($translation['missingKeys']) > 0) { + if ($verboseOutput && $translation['missingKeys']) { echo '| Missing Translations:'.\PHP_EOL; foreach ($translation['missingKeys'] as $id => $content) { @@ -243,7 +243,7 @@ function printTable($translations, $verboseOutput, bool $includeCompletedLanguag } $shouldBeClosed = true; } - if (true === $verboseOutput && count($translation['mismatches']) > 0) { + if ($verboseOutput && $translation['mismatches']) { echo '| Mismatches between trans-unit id and source:'.\PHP_EOL; foreach ($translation['mismatches'] as $id => $content) { diff --git a/data/web/inc/lib/vendor/symfony/translation/Resources/data/parents.json b/data/web/inc/lib/vendor/symfony/translation/Resources/data/parents.json index 24d4d119e..0197e451b 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Resources/data/parents.json +++ b/data/web/inc/lib/vendor/symfony/translation/Resources/data/parents.json @@ -18,29 +18,37 @@ "en_CM": "en_001", "en_CX": "en_001", "en_CY": "en_001", + "en_CZ": "en_150", "en_DE": "en_150", "en_DG": "en_001", "en_DK": "en_150", "en_DM": "en_001", + "en_EE": "en_150", "en_ER": "en_001", + "en_ES": "en_150", "en_FI": "en_150", "en_FJ": "en_001", "en_FK": "en_001", "en_FM": "en_001", + "en_FR": "en_150", "en_GB": "en_001", "en_GD": "en_001", + "en_GE": "en_150", "en_GG": "en_001", "en_GH": "en_001", "en_GI": "en_001", "en_GM": "en_001", + "en_GS": "en_001", "en_GY": "en_001", "en_HK": "en_001", + "en_HU": "en_150", "en_ID": "en_001", "en_IE": "en_001", "en_IL": "en_001", "en_IM": "en_001", "en_IN": "en_001", "en_IO": "en_001", + "en_IT": "en_150", "en_JE": "en_001", "en_JM": "en_001", "en_KE": "en_001", @@ -50,6 +58,8 @@ "en_LC": "en_001", "en_LR": "en_001", "en_LS": "en_001", + "en_LT": "en_150", + "en_LV": "en_150", "en_MG": "en_001", "en_MO": "en_001", "en_MS": "en_001", @@ -62,13 +72,17 @@ "en_NF": "en_001", "en_NG": "en_001", "en_NL": "en_150", + "en_NO": "en_150", "en_NR": "en_001", "en_NU": "en_001", "en_NZ": "en_001", "en_PG": "en_001", "en_PK": "en_001", + "en_PL": "en_150", "en_PN": "en_001", + "en_PT": "en_150", "en_PW": "en_001", + "en_RO": "en_150", "en_RW": "en_001", "en_SB": "en_001", "en_SC": "en_001", @@ -77,6 +91,7 @@ "en_SG": "en_001", "en_SH": "en_001", "en_SI": "en_150", + "en_SK": "en_150", "en_SL": "en_001", "en_SS": "en_001", "en_SX": "en_001", @@ -87,6 +102,7 @@ "en_TT": "en_001", "en_TV": "en_001", "en_TZ": "en_001", + "en_UA": "en_150", "en_UG": "en_001", "en_VC": "en_001", "en_VG": "en_001", @@ -119,6 +135,7 @@ "es_VE": "es_419", "ff_Adlm": "root", "hi_Latn": "en_IN", + "kk_Arab": "root", "ks_Deva": "root", "nb": "no", "nn": "no", diff --git a/data/web/inc/lib/vendor/symfony/translation/StaticMessage.php b/data/web/inc/lib/vendor/symfony/translation/StaticMessage.php new file mode 100644 index 000000000..ba1320562 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/translation/StaticMessage.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class StaticMessage implements TranslatableInterface +{ + public function __construct( + private string $message, + ) { + } + + public function getMessage(): string + { + return $this->message; + } + + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return $this->getMessage(); + } +} diff --git a/data/web/inc/lib/vendor/symfony/translation/Test/AbstractProviderFactoryTestCase.php b/data/web/inc/lib/vendor/symfony/translation/Test/AbstractProviderFactoryTestCase.php new file mode 100644 index 000000000..75e7dd2d3 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/translation/Test/AbstractProviderFactoryTestCase.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Translation\Exception\UnsupportedSchemeException; +use Symfony\Component\Translation\Provider\Dsn; +use Symfony\Component\Translation\Provider\ProviderFactoryInterface; + +abstract class AbstractProviderFactoryTestCase extends TestCase +{ + abstract public function createFactory(): ProviderFactoryInterface; + + /** + * @return iterable + */ + abstract public static function supportsProvider(): iterable; + + /** + * @return iterable + */ + abstract public static function createProvider(): iterable; + + /** + * @return iterable + */ + abstract public static function unsupportedSchemeProvider(): iterable; + + /** + * @dataProvider supportsProvider + */ + #[DataProvider('supportsProvider')] + public function testSupports(bool $expected, string $dsn) + { + $factory = $this->createFactory(); + + $this->assertSame($expected, $factory->supports(new Dsn($dsn))); + } + + /** + * @dataProvider createProvider + */ + #[DataProvider('createProvider')] + public function testCreate(string $expected, string $dsn) + { + $factory = $this->createFactory(); + $provider = $factory->create(new Dsn($dsn)); + + $this->assertSame($expected, (string) $provider); + } + + /** + * @dataProvider unsupportedSchemeProvider + */ + #[DataProvider('unsupportedSchemeProvider')] + public function testUnsupportedSchemeException(string $dsn, ?string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(UnsupportedSchemeException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } +} diff --git a/data/web/inc/lib/vendor/symfony/translation/Test/IncompleteDsnTestTrait.php b/data/web/inc/lib/vendor/symfony/translation/Test/IncompleteDsnTestTrait.php new file mode 100644 index 000000000..892f6bf72 --- /dev/null +++ b/data/web/inc/lib/vendor/symfony/translation/Test/IncompleteDsnTestTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test; + +use PHPUnit\Framework\Attributes\DataProvider; +use Symfony\Component\Translation\Exception\IncompleteDsnException; +use Symfony\Component\Translation\Provider\Dsn; + +trait IncompleteDsnTestTrait +{ + /** + * @return iterable + */ + abstract public static function incompleteDsnProvider(): iterable; + + /** + * @dataProvider incompleteDsnProvider + */ + #[DataProvider('incompleteDsnProvider')] + public function testIncompleteDsnException(string $dsn, ?string $message = null) + { + $factory = $this->createFactory(); + + $dsn = new Dsn($dsn); + + $this->expectException(IncompleteDsnException::class); + if (null !== $message) { + $this->expectExceptionMessage($message); + } + + $factory->create($dsn); + } +} diff --git a/data/web/inc/lib/vendor/symfony/translation/Test/ProviderFactoryTestCase.php b/data/web/inc/lib/vendor/symfony/translation/Test/ProviderFactoryTestCase.php index 95ffcb1e5..e82f32907 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Test/ProviderFactoryTestCase.php +++ b/data/web/inc/lib/vendor/symfony/translation/Test/ProviderFactoryTestCase.php @@ -12,15 +12,10 @@ namespace Symfony\Component\Translation\Test; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Translation\Dumper\XliffFileDumper; -use Symfony\Component\Translation\Exception\IncompleteDsnException; -use Symfony\Component\Translation\Exception\UnsupportedSchemeException; use Symfony\Component\Translation\Loader\LoaderInterface; -use Symfony\Component\Translation\Provider\Dsn; -use Symfony\Component\Translation\Provider\ProviderFactoryInterface; use Symfony\Component\Translation\TranslatorBagInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -28,9 +23,13 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; * A test case to ease testing a translation provider factory. * * @author Mathieu Santostefano + * + * @deprecated since Symfony 7.2, use AbstractProviderFactoryTestCase instead */ -abstract class ProviderFactoryTestCase extends TestCase +abstract class ProviderFactoryTestCase extends AbstractProviderFactoryTestCase { + use IncompleteDsnTestTrait; + protected HttpClientInterface $client; protected LoggerInterface|MockObject $logger; protected string $defaultLocale; @@ -38,20 +37,8 @@ abstract class ProviderFactoryTestCase extends TestCase protected XliffFileDumper|MockObject $xliffFileDumper; protected TranslatorBagInterface|MockObject $translatorBag; - abstract public function createFactory(): ProviderFactoryInterface; - /** - * @return iterable - */ - abstract public static function supportsProvider(): iterable; - - /** - * @return iterable - */ - abstract public static function createProvider(): iterable; - - /** - * @return iterable + * @return iterable */ public static function unsupportedSchemeProvider(): iterable { @@ -59,68 +46,13 @@ abstract class ProviderFactoryTestCase extends TestCase } /** - * @return iterable + * @return iterable */ public static function incompleteDsnProvider(): iterable { return []; } - /** - * @dataProvider supportsProvider - */ - public function testSupports(bool $expected, string $dsn) - { - $factory = $this->createFactory(); - - $this->assertSame($expected, $factory->supports(new Dsn($dsn))); - } - - /** - * @dataProvider createProvider - */ - public function testCreate(string $expected, string $dsn) - { - $factory = $this->createFactory(); - $provider = $factory->create(new Dsn($dsn)); - - $this->assertSame($expected, (string) $provider); - } - - /** - * @dataProvider unsupportedSchemeProvider - */ - public function testUnsupportedSchemeException(string $dsn, ?string $message = null) - { - $factory = $this->createFactory(); - - $dsn = new Dsn($dsn); - - $this->expectException(UnsupportedSchemeException::class); - if (null !== $message) { - $this->expectExceptionMessage($message); - } - - $factory->create($dsn); - } - - /** - * @dataProvider incompleteDsnProvider - */ - public function testIncompleteDsnException(string $dsn, ?string $message = null) - { - $factory = $this->createFactory(); - - $dsn = new Dsn($dsn); - - $this->expectException(IncompleteDsnException::class); - if (null !== $message) { - $this->expectExceptionMessage($message); - } - - $factory->create($dsn); - } - protected function getClient(): HttpClientInterface { return $this->client ??= new MockHttpClient(); diff --git a/data/web/inc/lib/vendor/symfony/translation/Test/ProviderTestCase.php b/data/web/inc/lib/vendor/symfony/translation/Test/ProviderTestCase.php index a8fa0b8bb..b7862ba38 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Test/ProviderTestCase.php +++ b/data/web/inc/lib/vendor/symfony/translation/Test/ProviderTestCase.php @@ -11,13 +11,17 @@ namespace Symfony\Component\Translation\Test; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Loader\ArrayLoader; use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\Provider\ProviderInterface; +use Symfony\Component\Translation\TranslatorBag; use Symfony\Component\Translation\TranslatorBagInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -45,6 +49,7 @@ abstract class ProviderTestCase extends TestCase /** * @dataProvider toStringProvider */ + #[DataProvider('toStringProvider')] public function testToString(ProviderInterface $provider, string $expected) { $this->assertSame($expected, (string) $provider); @@ -57,12 +62,12 @@ abstract class ProviderTestCase extends TestCase protected function getLoader(): LoaderInterface { - return $this->loader ??= $this->createMock(LoaderInterface::class); + return $this->loader ??= new ArrayLoader(); } protected function getLogger(): LoggerInterface { - return $this->logger ??= $this->createMock(LoggerInterface::class); + return $this->logger ??= new NullLogger(); } protected function getDefaultLocale(): string @@ -72,11 +77,11 @@ abstract class ProviderTestCase extends TestCase protected function getXliffFileDumper(): XliffFileDumper { - return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class); + return $this->xliffFileDumper ??= new XliffFileDumper(); } protected function getTranslatorBag(): TranslatorBagInterface { - return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class); + return $this->translatorBag ??= new TranslatorBag(); } } diff --git a/data/web/inc/lib/vendor/symfony/translation/TranslatableMessage.php b/data/web/inc/lib/vendor/symfony/translation/TranslatableMessage.php index c591e68c2..746380379 100644 --- a/data/web/inc/lib/vendor/symfony/translation/TranslatableMessage.php +++ b/data/web/inc/lib/vendor/symfony/translation/TranslatableMessage.php @@ -19,19 +19,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class TranslatableMessage implements TranslatableInterface { - private string $message; - private array $parameters; - private ?string $domain; - - public function __construct(string $message, array $parameters = [], ?string $domain = null) - { - $this->message = $message; - $this->parameters = $parameters; - $this->domain = $domain; + public function __construct( + private string $message, + private array $parameters = [], + private ?string $domain = null, + ) { } + /** + * @deprecated since Symfony 7.4 + */ public function __toString(): string { + trigger_deprecation('symfony/translation', '7.4', 'Method "%s()" is deprecated.', __METHOD__); + return $this->getMessage(); } @@ -52,9 +53,13 @@ class TranslatableMessage implements TranslatableInterface public function trans(TranslatorInterface $translator, ?string $locale = null): string { - return $translator->trans($this->getMessage(), array_map( - static fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter, - $this->getParameters() - ), $this->getDomain(), $locale); + $parameters = $this->getParameters(); + foreach ($parameters as $k => $v) { + if ($v instanceof TranslatableInterface) { + $parameters[$k] = $v->trans($translator, $locale); + } + } + + return $translator->trans($this->getMessage(), $parameters, $this->getDomain(), $locale); } } diff --git a/data/web/inc/lib/vendor/symfony/translation/Translator.php b/data/web/inc/lib/vendor/symfony/translation/Translator.php index 1973d079f..1c3935200 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Translator.php +++ b/data/web/inc/lib/vendor/symfony/translation/Translator.php @@ -36,7 +36,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA /** * @var MessageCatalogueInterface[] */ - protected $catalogues = []; + protected array $catalogues = []; private string $locale; @@ -54,36 +54,39 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA private MessageFormatterInterface $formatter; - private ?string $cacheDir; - - private bool $debug; - - private array $cacheVary; - private ?ConfigCacheFactoryInterface $configCacheFactory; private array $parentLocales; private bool $hasIntlFormatter; + /** + * @var array + */ + private array $globalParameters = []; + + /** + * @var array + */ + private array $globalTranslatedParameters = []; + /** * @throws InvalidArgumentException If a locale contains invalid characters */ - public function __construct(string $locale, ?MessageFormatterInterface $formatter = null, ?string $cacheDir = null, bool $debug = false, array $cacheVary = []) - { + public function __construct( + string $locale, + ?MessageFormatterInterface $formatter = null, + private ?string $cacheDir = null, + private bool $debug = false, + private array $cacheVary = [], + ) { $this->setLocale($locale); $this->formatter = $formatter ??= new MessageFormatter(); - $this->cacheDir = $cacheDir; - $this->debug = $debug; - $this->cacheVary = $cacheVary; $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface; } - /** - * @return void - */ - public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory): void { $this->configCacheFactory = $configCacheFactory; } @@ -92,10 +95,8 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA * Adds a Loader. * * @param string $format The name of the loader (@see addResource()) - * - * @return void */ - public function addLoader(string $format, LoaderInterface $loader) + public function addLoader(string $format, LoaderInterface $loader): void { $this->loaders[$format] = $loader; } @@ -106,11 +107,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA * @param string $format The name of the loader (@see addLoader()) * @param mixed $resource The resource name * - * @return void - * * @throws InvalidArgumentException If the locale contains invalid characters */ - public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null) + public function addResource(string $format, mixed $resource, string $locale, ?string $domain = null): void { $domain ??= 'messages'; @@ -119,17 +118,14 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA $this->resources[$locale][] = [$format, $resource, $domain]; - if (\in_array($locale, $this->fallbackLocales)) { + if (\in_array($locale, $this->fallbackLocales, true)) { $this->catalogues = []; } else { unset($this->catalogues[$locale]); } } - /** - * @return void - */ - public function setLocale(string $locale) + public function setLocale(string $locale): void { $this->assertValidLocale($locale); $this->locale = $locale; @@ -145,11 +141,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA * * @param string[] $locales * - * @return void - * * @throws InvalidArgumentException If a locale contains invalid characters */ - public function setFallbackLocales(array $locales) + public function setFallbackLocales(array $locales): void { // needed as the fallback locales are linked to the already loaded catalogues $this->catalogues = []; @@ -171,6 +165,17 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA return $this->fallbackLocales; } + public function addGlobalParameter(string $id, string|int|float|TranslatableInterface $value): void + { + $this->globalParameters[$id] = $value; + $this->globalTranslatedParameters = []; + } + + public function getGlobalParameters(): array + { + return $this->globalParameters; + } + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { if (null === $id || '' === $id) { @@ -190,7 +195,24 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA } } - $parameters = array_map(fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($this, $locale) : $parameter, $parameters); + foreach ($parameters as $key => $value) { + if ($value instanceof TranslatableInterface) { + $parameters[$key] = $value->trans($this, $locale); + } + } + + if (null === $globalParameters = &$this->globalTranslatedParameters[$locale]) { + $globalParameters = $this->globalParameters; + foreach ($globalParameters as $key => $value) { + if ($value instanceof TranslatableInterface) { + $globalParameters[$key] = $value->trans($this, $locale); + } + } + } + + if ($globalParameters) { + $parameters += $globalParameters; + } $len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX); if ($this->hasIntlFormatter @@ -233,10 +255,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA return $this->loaders; } - /** - * @return void - */ - protected function loadCatalogue(string $locale) + protected function loadCatalogue(string $locale): void { if (null === $this->cacheDir) { $this->initializeCatalogue($locale); @@ -245,10 +264,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA } } - /** - * @return void - */ - protected function initializeCatalogue(string $locale) + protected function initializeCatalogue(string $locale): void { $this->assertValidLocale($locale); @@ -290,18 +306,17 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA $this->initializeCatalogue($locale); $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]); - $content = sprintf(<<getAllMessages($this->catalogues[$locale]), true), $fallbackContent @@ -321,12 +336,11 @@ EOF $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); - $fallbackContent .= sprintf(<<<'EOF' -$catalogue%s = new MessageCatalogue('%s', %s); -$catalogue%s->addFallbackCatalogue($catalogue%s); + $fallbackContent .= \sprintf(<<<'EOF' + $catalogue%s = new MessageCatalogue('%s', %s); + $catalogue%s->addFallbackCatalogue($catalogue%s); -EOF - , + EOF, $fallbackSuffix, $fallback, var_export($this->getAllMessages($fallbackCatalogue), true), @@ -342,7 +356,7 @@ EOF private function getCatalogueCachePath(string $locale): string { - return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php'; + return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('xxh128', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php'; } /** @@ -356,10 +370,10 @@ EOF foreach ($this->resources[$locale] as $resource) { if (!isset($this->loaders[$resource[0]])) { if (\is_string($resource[1])) { - throw new RuntimeException(sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1])); + throw new RuntimeException(\sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1])); } - throw new RuntimeException(sprintf('No loader is registered for the "%s" format.', $resource[0])); + throw new RuntimeException(\sprintf('No loader is registered for the "%s" format.', $resource[0])); } $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); } @@ -384,10 +398,7 @@ EOF } } - /** - * @return array - */ - protected function computeFallbackLocales(string $locale) + protected function computeFallbackLocales(string $locale): array { $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); @@ -431,14 +442,12 @@ EOF /** * Asserts that the locale is valid, throws an Exception if not. * - * @return void - * * @throws InvalidArgumentException If the locale contains invalid characters */ - protected function assertValidLocale(string $locale) + protected function assertValidLocale(string $locale): void { if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { - throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); + throw new InvalidArgumentException(\sprintf('Invalid "%s" locale.', $locale)); } } diff --git a/data/web/inc/lib/vendor/symfony/translation/Util/ArrayConverter.php b/data/web/inc/lib/vendor/symfony/translation/Util/ArrayConverter.php index 64e15b485..2fc666d4a 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Util/ArrayConverter.php +++ b/data/web/inc/lib/vendor/symfony/translation/Util/ArrayConverter.php @@ -27,7 +27,7 @@ class ArrayConverter { /** * Converts linear messages array to tree-like array. - * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']]. + * For example: ['foo.bar' => 'value'] will be converted to ['foo' => ['bar' => 'value']]. * * @param array $messages Linear messages array */ diff --git a/data/web/inc/lib/vendor/symfony/translation/Util/XliffUtils.php b/data/web/inc/lib/vendor/symfony/translation/Util/XliffUtils.php index 335c34beb..1ce8f185a 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Util/XliffUtils.php +++ b/data/web/inc/lib/vendor/symfony/translation/Util/XliffUtils.php @@ -31,7 +31,6 @@ class XliffUtils */ public static function getVersionNumber(\DOMDocument $dom): string { - /** @var \DOMNode $xliff */ foreach ($dom->getElementsByTagName('xliff') as $xliff) { $version = $xliff->attributes->getNamedItem('version'); if ($version) { @@ -41,7 +40,7 @@ class XliffUtils $namespace = $xliff->attributes->getNamedItem('xmlns'); if ($namespace) { if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) { - throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s".', $namespace)); + throw new InvalidArgumentException(\sprintf('Not a valid XLIFF namespace "%s".', $namespace)); } return substr($namespace, 34); @@ -83,6 +82,24 @@ class XliffUtils return []; } + public static function getErrorsAsString(array $xmlErrors): string + { + $errorsAsString = ''; + + foreach ($xmlErrors as $error) { + $errorsAsString .= \sprintf("[%s %s] %s (in %s - line %d, column %d)\n", + \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', + $error['code'], + $error['message'], + $error['file'], + $error['line'], + $error['column'] + ); + } + + return $errorsAsString; + } + private static function shouldEnableEntityLoader(): bool { static $dom, $schema; @@ -96,7 +113,7 @@ class XliffUtils }); $schema = ' - + '; file_put_contents($tmpfile, ' @@ -108,22 +125,17 @@ class XliffUtils return !@$dom->schemaValidateSource($schema); } - public static function getErrorsAsString(array $xmlErrors): string + private static function getFileUrl(string $path): string { - $errorsAsString = ''; - - foreach ($xmlErrors as $error) { - $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n", - \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', - $error['code'], - $error['message'], - $error['file'], - $error['line'], - $error['column'] - ); + if ('\\' === \DIRECTORY_SEPARATOR) { + $parts = explode('/', str_replace('\\', '/', $path)); + $drive = array_shift($parts).'/'; + } else { + $parts = explode('/', $path); + $drive = ''; } - return $errorsAsString; + return 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); } private static function getSchema(string $xliffVersion): string @@ -135,7 +147,7 @@ class XliffUtils $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd'); $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; } else { - throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); + throw new InvalidArgumentException(\sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); } return self::fixXmlLocation($schemaSource, $xmlUri); @@ -146,23 +158,27 @@ class XliffUtils */ private static function fixXmlLocation(string $schemaSource, string $xmlUri): string { - $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd'; - $parts = explode('/', $newPath); - $locationstart = 'file:///'; - if (0 === stripos($newPath, 'phar://')) { - $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); - if ($tmpfile) { - copy($newPath, $tmpfile); - $parts = explode('/', str_replace('\\', '/', $tmpfile)); + static $newPath; + + if (null === $newPath) { + $path = __DIR__.'/../Resources/schemas/xml.xsd'; + + if (0 !== stripos($path, 'phar://')) { + $newPath = self::getFileUrl($path); + } elseif ($tmpfile = tempnam(sys_get_temp_dir(), 'symfony')) { + copy($path, $tmpfile); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $newPath = self::getFileUrl($tmpfile); } else { + $parts = explode('/', '\\' === \DIRECTORY_SEPARATOR ? str_replace('\\', '/', $path) : $path); array_shift($parts); - $locationstart = 'phar:///'; + $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = 'phar:///'.$drive.implode('/', array_map('rawurlencode', $parts)); } } - $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; - $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); - return str_replace($xmlUri, $newPath, $schemaSource); } diff --git a/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriter.php b/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriter.php index 61e03cb0e..5f58e681e 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriter.php +++ b/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriter.php @@ -30,10 +30,8 @@ class TranslationWriter implements TranslationWriterInterface /** * Adds a dumper to the writer. - * - * @return void */ - public function addDumper(string $format, DumperInterface $dumper) + public function addDumper(string $format, DumperInterface $dumper): void { $this->dumpers[$format] = $dumper; } @@ -52,21 +50,19 @@ class TranslationWriter implements TranslationWriterInterface * @param string $format The format to use to dump the messages * @param array $options Options that are passed to the dumper * - * @return void - * * @throws InvalidArgumentException */ - public function write(MessageCatalogue $catalogue, string $format, array $options = []) + public function write(MessageCatalogue $catalogue, string $format, array $options = []): void { if (!isset($this->dumpers[$format])) { - throw new InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format)); + throw new InvalidArgumentException(\sprintf('There is no dumper associated with format "%s".', $format)); } // get the right dumper $dumper = $this->dumpers[$format]; - if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) { - throw new RuntimeException(sprintf('Translation Writer was not able to create directory "%s".', $options['path'])); + if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0o777, true) && !is_dir($options['path'])) { + throw new RuntimeException(\sprintf('Translation Writer was not able to create directory "%s".', $options['path'])); } // save diff --git a/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriterInterface.php b/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriterInterface.php index 5ebb9794a..b3062827b 100644 --- a/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriterInterface.php +++ b/data/web/inc/lib/vendor/symfony/translation/Writer/TranslationWriterInterface.php @@ -27,9 +27,7 @@ interface TranslationWriterInterface * @param string $format The format to use to dump the messages * @param array $options Options that are passed to the dumper * - * @return void - * * @throws InvalidArgumentException */ - public function write(MessageCatalogue $catalogue, string $format, array $options = []); + public function write(MessageCatalogue $catalogue, string $format, array $options = []): void; } diff --git a/data/web/inc/lib/vendor/symfony/translation/composer.json b/data/web/inc/lib/vendor/symfony/translation/composer.json index af6f7a3df..f86657478 100644 --- a/data/web/inc/lib/vendor/symfony/translation/composer.json +++ b/data/web/inc/lib/vendor/symfony/translation/composer.json @@ -16,35 +16,36 @@ } ], "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5|^3.0" + "symfony/translation-contracts": "^2.5.3|^3.3", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "nikic/php-parser": "^4.18|^5.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "nikic/php-parser": "^5.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/routing": "^6.4|^7.0|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/config": "<5.4", - "symfony/dependency-injection": "<5.4", + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.4", "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<5.4", - "symfony/yaml": "<5.4", - "symfony/console": "<5.4" + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4", + "symfony/console": "<6.4" }, "provide": { "symfony/translation-implementation": "2.3|3.0" diff --git a/data/web/inc/lib/vendor/twig/twig/CHANGELOG b/data/web/inc/lib/vendor/twig/twig/CHANGELOG index 44c79b133..09d38e601 100644 --- a/data/web/inc/lib/vendor/twig/twig/CHANGELOG +++ b/data/web/inc/lib/vendor/twig/twig/CHANGELOG @@ -1,3 +1,306 @@ +# 3.30.0 (2026-09-25) + + * Fix `split`, `random`, and `shuffle` merging a trailing newline into the last character of a string + * Speed up splitting a string into characters in `split`, `random`, and `shuffle` + * Speed up escaping by fetching the escaper runtime once per template + * Speed up adding extensions to an environment + * Fix the escaping safe analysis retaining every compiled template node for the lifetime of the environment + * Speed up loading a template that the environment has already loaded + * Speed up rendering by compiling a cheaper generator guard into templates + * Speed up compiling filter, function, and test calls + * Fix `IntlExtension` letting the pattern derived from a date formatter prototype override an explicit locale + * Fix `IntlExtension` not honoring the locale of a date formatter prototype configured with no date and time styles + * Speed up macro calls + * Fix `TemplateWrapper::unwrap()` failing when called without arguments, which is now deprecated + * Add `TemplateWrapper::getDefaultEscapeStrategy()` to know the escaping strategy a template was compiled with + * Report a clear error when `random`, `reverse`, `shuffle`, and `split` receive a string that is not valid UTF-8 + * Fix the deprecation about omitting parentheses when calling a macro being reported twice for the same call + * Add the macro name to the deprecation about omitting parentheses when calling a macro + * Deprecate cloning a `Twig\Environment` instance + * Fix array access with a `Stringable` key on subclasses of `ArrayObject` and `ArrayIterator` + +# 3.29.0 (2026-09-18) + + * Fix the PHP warning and cryptic error when a block or a macro rendered on its own uses macros imported in the template body + * Fix `{% cache %}` always missing in the Symfony bundle when `framework.cache.app` uses a natively tag aware adapter + * Fix the sandbox resolving `use` trait templates before checking that the `use` tag is allowed + * Fix `html_attr` dropping `style` declarations whose value is `0`, `0.0` or `'0'` + * Fix the `default` filter fallback emitting an undefined variable warning when it uses the null-safe operator + * Fix the `matches` operator silently treating PCRE execution errors as non-matches; it now throws a `RuntimeError` + * Fix `TemplateWrapper::streamBlock()`, `TemplateWrapper::hasBlock()`, and `TemplateWrapper::getBlockNames()` omitting environment globals + * Fix exceptions from dynamic parent expressions escaping without template context + * Add the `BlockChain` class to compose blocks from multiple templates without using template internals + * Fix `TemplateWrapper::hasBlock()` and `TemplateWrapper::getBlockNames()` losing the `extends` line when the parent template does not exist + * Fix an output buffer leak when a parent block rendered in an expression throws in non-yield mode + * Add the `HtmlExtension::htmlAttrValue()` method to resolve a single HTML attribute value the way the `html_attr` function renders it + * Fix `html_attr` JSON encoding a `Stringable` value in a `data-*` attribute instead of using its string representation + * Add documentation comments to attach metadata to nodes (experimental) + * Fix destructuring patterns containing no variables (empty patterns or sequences with only empty slots) triggering a PHP fatal error instead of a `SyntaxError` + * Fix object and mapping destructuring evaluating the right-hand expression more than once + * Fix sequence destructuring of iterators throwing a `TypeError` + * Fix `MissingExtensionSuggestor` not suggesting the `twig/*-extra` package to install for some `html-extra`, `intl-extra`, and `string-extra` filters and functions + * Add `TempestMarkdown` to use `tempest/markdown` as the `markdown_to_html` converter + * Add the `include_only` function to render a template without giving it access to the current context + * Add the `Twig\Sandbox\SandboxInterface` interface and `Twig\Sandbox\Sandbox` class to render untrusted templates through a dedicated, always-sandboxed environment crafted for it + * Reject `TemplateWrapper` instances created by another `Environment` + * Add the `Twig\Extension\SandboxBridgeExtension` to render sandboxed templates from trusted templates with an explicit output escaping strategy + * Extract the sandbox runtime enforcement into a new internal `Twig\Sandbox\SecurityChecker` class used by compiled templates and `CoreExtension` + * Mark `SandboxExtension` as internal, use `Twig\Sandbox\Sandbox` instead + * Deprecate the `sandboxed` argument of the `include` function, use `Twig\Sandbox\Sandbox` instead + * Deprecate `SandboxExtension::enableSandbox()`, `disableSandbox()`, and `isSandboxedGlobally()` + * Normalize destructuring variable AST nodes as assignment targets + * Fix `IntlExtension` ignoring explicit date/time formats and configured calendars when using a date formatter prototype + * Add a `format_list` filter to `IntlExtension` to format a list of strings using PHP 8.5's `IntlListFormatter` + * Fix array access with a `Stringable` key for `ArrayObject` and `ArrayIterator` while preserving object keys for `SplObjectStorage` + * Fix duplicated macro argument names triggering a PHP fatal error instead of a `SyntaxError` + * Deprecate defining a macro more than once in the same template + * Deprecate `TemplateVariable` and `AssignTemplateVariable`; use `MacroVariable` and `AssignMacroVariable` instead + * Deprecate calling or testing a macro with a name whose case differs from its definition; macro names will be case-sensitive in 4.0 + * Deprecate omitting parentheses when calling a macro; it will throw a `SyntaxError` in 4.0 + * Deprecate using parentheses when testing a macro with the `defined` test; it will throw a `SyntaxError` in 4.0 + * Deprecate calling a macro without a value for an argument that has no default value; the argument will be required in 4.0 + * Deprecate passing extra or unknown arguments to a macro that does not declare a variadic argument; it will throw in 4.0 + * Add support for declaring an explicit variadic macro argument (`{% macro foo(a, ...rest) %}`) + * Compile macros as closures stored in a per-template registry instead of `macro_`-prefixed PHP methods + * Represent imported macro namespaces with a narrow capability object instead of template instances + * Mark `Twig\Node\MacroNode` as `@final`; it will be final in Twig 4.0 + * Deprecate not passing a `MacrosNode` instance as the macros of a `ModuleNode` constructor + * Change `MacroReferenceExpression` to take the bare macro name instead of a `macro_`-prefixed method name + * Deprecate resolving a macro through a `macro_`-prefixed name; pass the bare macro name to `MacroReferenceExpression` + +# 3.28.0 (2026-07-03) + + * Render backed enums using their backing value in the `html_attr` function + * Fix empty Markup values being treated as truthy in and, or, xor, not, ternary, and elvis expressions + * Fix a PHP 8.5 `chr()` deprecation when decoding an octal string escape sequence larger than `\377` (such as `"\777"`) + * Mark `Twig\Markup` as `@final`; it will be final in Twig 4.0 + * Reduce memory usage and speed up the context restoration compiled at the end of `for` loops + * Allow calling a macro with a dynamic name via the dot operator (`macros.(name)(args)`) + * Report the column number in syntax errors and expose it via `Error::getTemplateColumn()` + * Track the source offset of each token and expose it via `Token::getOffset()` + * Fix nested `block()` calls to resolve against the overriding template when a block rendered through `block(name, template)` calls `parent()` + * Deprecate the possibility to use a `block` tag within a capture node (like `set`) + * Deprecate the possibility to use a `block` tag within a capture node (like `set`) in child templates + * Deprecate using a `macro`, `extends`, or `use` tag outside the root of a template + * Stop reporting a skipped test in `IntegrationTestCase` when there is no legacy test to run + * Fix `markdown_to_html` to strip the indentation shared by all lines instead of mangling content that starts with a blank line + * Make the `IntegrationTestCase` and `NodeTestCase` test helpers compatible with PHPUnit 11 + * Cast printed expressions to string so values that cannot be converted to a string (arrays, non-`Stringable` objects, ...) report a usable stack trace at the print location + * Make the `include()` function return a `Markup` object so an assigned result is not re-escaped when printed + * Skip the sandbox `__toString` check on arguments whose PHP parameter type cannot implicitly coerce to string + * Document the criteria for marking a callable or tag as always allowed in a sandbox, and the list of built-in tags, filters, and functions that will be always allowed in Twig 4.0 + * Add an `always_allowed_in_sandbox` option for filters, functions, and tests, and an `isAlwaysAllowedInSandbox()` method for token parsers, to let authors mark callables and tags that are always allowed in sandbox mode without explicit allow-listing + * Add an allow-list for tests to `Twig\Sandbox\SecurityPolicy`, with the safe built-in tests flagged as always allowed so they keep working without allow-listing + +# 3.27.1 (2026-05-30) + + * Fix array access with a `Stringable` key to coerce the key to string consistently instead of throwing in the optimized path + * Fix sandbox replacing `IteratorAggregate` arguments (e.g. Symfony's `FormView`) by a plain array + +# 3.27.0 (2026-05-27) + + * Add a strict mode to `Twig\Sandbox\SecurityPolicy` to opt-in to the 4.0 behavior for the `extends`/`use` tags and the `parent`/`block`/`attribute` functions, which are otherwise still implicitly allowed in a sandbox + * Deprecate the fact that the `parent`, `block`, and `attribute` functions are always allowed in a sandboxed template + * Fix sandbox filter/tag/function allow-list bypass when the sandbox state changed between renders of a cached `Template` instance + * Fix PHP 8.1+ implicit float-to-int deprecation triggered by sandboxed `ArrayAccess` attribute access with a float key + * Restrict allowed classes in `Twig\Profiler\Profile::unserialize()` to prevent arbitrary class instantiation + * Escape root profile name in `HtmlDumper` + * Fix sandbox bypass in deprecated internal wrappers `twig_array_some()`, `twig_array_every()`, and `twig_check_arrow_in_sandbox()` (`src/Resources/core.php`) + * Deprecate the `Twig\Sandbox\SourcePolicyInterface` interface with no replacement + * Fix sandbox bypass in the "column" filter when sandboxing is enabled via `SourcePolicyInterface` + * Fix sandbox `__toString` bypass via `Traversable` arguments to the `join` and `replace` filters (also covers containers that implement both `Stringable` and `Traversable`) + * Fix sandbox `__toString` bypass via the `in` and `not in` operators + * Prevent a stack overflow in `SandboxExtension::ensureToStringAllowed()` when a self-referencing iterable is passed to a sandboxed template + * Add support for any expression as a dynamic mapping key (attribute access, filters, ...) + * Fix sandbox `__toString` policy bypass via dynamic mapping keys + +# 3.26.0 (2026-05-20) + + * Document that the sandbox doesn't protect against resource exhaustion + * Document `template_from_string` caveats when used in a sandboxed environment + * Add docs on `Markup` about the goal of this class in the context of a sandbox + * Pre-escape HTML input on the `spaceless` filter + * Pre-escape HTML input on `inline_css` and `inky_to_html` filters + * Fix XSS by adjusting `is_safe` annotation on HTML-emitting filters + * [Profiler] Escape template and profile names in `HtmlDumper` + * Fix unbounded memoisation of `IntlDateFormatter` / `NumberFormatter` + * Fix sandbox bypass in the "column" filter + * Fix sandbox bypass in the `{% sandbox %}` tag when including a preloaded template + * Fix sandbox bypass: PHP code injection via `{% use %}` template name + * Fix sandbox bypass: PHP code injection via `_self` / import macro reference + * Fix sandbox bypass in object destructuring assignment + * Fix sandbox bypass: propagate `Source` to `checkArrow` for source-policy sandboxing + * Encode single quotes as `\x27` in `Compiler::string()` as a defense-in-depth measure + * Fix sandbox `__toString` bypasses + * Add `Twig\Node\CoercesChildrenToStringInterface` to let nodes declare which of their child nodes will be string-coerced at runtime so the sandbox wraps them with a `__toString` check + +# 3.25.0 (2026-05-17) + + * Add a `needs_is_sandboxed` option for filters, functions, and tests + * Use deterministic suffixes for generated embed classes + * Lazy-load `EscaperRuntime` in `EscaperExtension` + +# 3.24.0 (2026-03-17) + + * Deprecate not implementing the `getOperatorTokens()` method in `ExpressionParserInterface` implementations + * Deprecate passing a non-`AbstractExpression` node to `Twig\Node\Expression\Binary\MatchesBinary` constructor + * Deprecate passing a non-`AbstractExpression` node to `Parser::setParent()` + * Add support for renaming variables in object destructuring (`{name: userName} = user`) + * Add `html_attr_relaxed` escaping strategy that preserves :, @, [, and ] for front-end framework attribute names + * Add support for short-circuiting in null-safe operator chains + * Add the `html_attr` function and `html_attr_merge` as well as `html_attr_type` filters + +# 3.23.0 (2026-01-23) + + * Add `=` assignment operator (allows to set variables in expression or to replace the short-form of the set tag) + * Add sequence, mapping, and object destructuring + * Add `?.` null-safe operator + * Add `===` and `!==` operators (equivalent to the `same as` and `not same as` tests) + * Fix opcache preload warning for unlinked anonymous class + * Fix spread operator behavior + +# 3.22.2 (2025-12-14) + + * Fix "cycle" with non-countable ArrayAccess + Traversable objects + * Use "getShareDir" as an indicator of Symfony version in Symfony bundle + * Fix escaper compatibility with PHP 8.5 + +# 3.22.1 (2025-11-16) + + * Add support for Symfony 8 + +# 3.22.0 (2025-10-29) + + * Add support for two words test in guard tag + * Add `Environment::registerUndefinedTestCallback()` + * Fix compatibility with Symfony 8 + * Fix accessing arrays with stringable objects as key + * Avoid errors when failing to guess the template info for an error + * Fix expression parser compatibility layer + * Fix compiling 'index' with repr (not string) in EmbedNode + * Update configuration keys + allow extra keys for CommonMark extensions + * Allow usage of other Markdown converters than CommonMark in LeagueMarkdown + +# 3.21.1 (2025-05-03) + + * Fix ExtensionSet usage of BinaryOperatorExpressionParser + +# 3.21.0 (2025-05-02) + + * Fix wrong array index + * Deprecate `Template::loadTemplate()` + * Fix testing and expression when it evaluates to an instance of `Markup` + * Add `ReturnPrimitiveTypeInterface` (and sub-interfaces for number, boolean, string, and array) + * Add `SupportDefinedTestInterface` for expression nodes supporting the `defined` test + * Deprecate using the `|` operator in an expression with `+` or `-` without using parentheses to clarify precedence + * Deprecate operator precedence outside of the [0, 512] range + * Introduce expression parser classes to describe operators and operands provided by extensions + instead of arrays (it comes with many deprecations that are documented in + the ``deprecated`` documentation chapter) + * Deprecate the `Twig\ExpressionParser`, and `Twig\OperatorPrecedenceChange` classes + * Add attributes `AsTwigFilter`, `AsTwigFunction`, and `AsTwigTest` to ease extension development + +# 3.20.0 (2025-02-13) + + * Fix support for ignoring syntax errors in an undefined handler in guard + * Add configuration for Commonmark + * Fix wrong array index + * Bump minimum PHP version to 8.1 + * Add support for registering callbacks for undefined functions, filters or token parsers in the IntegrationTestCase + * Use correct line number for `ForElseNode` + * Fix timezone conversion on strings + +# 3.19.0 (2025-01-28) + + * Fix a security issue where escaping was missing when using `??` + * Deprecate `Token::getType()`, use `Token::test()` instead + * Add `Token::toEnglish()` + * Add `ForElseNode` + * Deprecate `Twig\ExpressionParser::parseOnlyArguments()` and + `Twig\ExpressionParser::parseArguments()` (use + `Twig\ExpressionParser::parseNamedArguments()` instead) + * Fix `constant()` behavior when used with `??` + * Add the `invoke` filter + * Make `{}` optional for the `types` tag + * Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes + * Ignore static properties when using the dot operator + +# 3.18.0 (2024-12-29) + + * Support for invoking closures + * Fix unary operator precedence change + * Ignore `SyntaxError` exceptions from undefined handlers when using the `guard` tag + * Add a way to stream template rendering (`TemplateWrapper::stream()` and `TemplateWrapper::streamBlock()`) + +# 3.17.1 (2024-12-12) + + * Fix the null coalescing operator when the test returns null + * Fix the Elvis operator when used as '? :' instead of '?:' + +# 3.17.0 (2024-12-10) + + * Fix ArrayAccess with objects as keys + * Support underscores in number literals + * Deprecate `ConditionalExpression` and `NullCoalesceExpression` (use `ConditionalTernary` and `NullCoalesceBinary` instead) + +# 3.16.0 (2024-11-29) + + * Deprecate `InlinePrint` + * Fix having macro variables starting with an underscore + * Deprecate not passing a `Source` instance to `TokenStream` + * Deprecate returning `null` from `TwigFilter::getSafe()` and `TwigFunction::getSafe()`, return `[]` instead + +# 3.15.0 (2024-11-17) + + * [BC BREAK] Add support for accessing class constants with the dot operator; + this can be a BC break if you don't use UPPERCASE constant names + * Add Spanish inflector support for the `plural` and `singular` filters in the String extension + * Deprecate `TempNameExpression` in favor of `LocalVariable` + * Deprecate `NameExpression` in favor of `ContextVariable` + * Deprecate `AssignNameExpression` in favor of `AssignContextVariable` + * Remove `MacroAutoImportNodeVisitor` + * Deprecate `MethodCallExpression` in favor of `MacroReferenceExpression` + * Fix support for the "is defined" test on `_self.xxx` (auto-imported) macros + * Fix support for the "is defined" test on inherited macros + * Add named arguments support for the dot operator arguments (`foo.bar(some: arg)`) + * Add named arguments support for macros + * Add a new `guard` tag that allows to test if some Twig callables are available at compilation time + * Allow arrow functions everywhere + * Deprecate passing a string or an array to Twig callable arguments accepting arrow functions (pass a `\Closure`) + * Add support for triggering deprecations for future operator precedence changes + * Deprecate using the `not` unary operator in an expression with ``*``, ``/``, ``//``, or ``%`` without using explicit parentheses to clarify precedence + * Deprecate using the `??` binary operator without explicit parentheses + * Deprecate using the `~` binary operator in an expression with `+` or `-` without using parentheses to clarify precedence + * Deprecate not passing `AbstractExpression` args to most constructor arguments for classes extending `AbstractExpression` + * Fix `power` expressions with a negative number in parenthesis (`(-1) ** 2`) + * Deprecate instantiating `Node` directly. Use `EmptyNode` or `Nodes` instead. + * Add support for inline comments + * Add `Profile::getStartTime()` and `Profile::getEndTime()` + * Fix "ignore missing" when used on an "embed" tag + * Fix the possibility to override an aliased block (via use) + * Add template cache hot reload + * Allow Twig callable argument names to be free-form (snake-case or camelCase) independently of the PHP callable signature + They were automatically converted to snake-cased before + * Deprecate the `attribute` function; use the `.` notation and wrap the name with parenthesis instead + * Add support for argument unpackaging + * Add JSON support for the file extension escaping strategy + * Support Markup instances (and any other \Stringable) as dynamic mapping keys + * Deprecate the `sandbox` tag + * Improve the way one can deprecate a Twig callable (use `deprecation_info` instead of the other callable options) + * Add the `enum` function + * Add support for logical `xor` operator + +# 3.14.2 (2024-11-07) + + * Fix an infinite recursion in the sandbox code + +# 3.14.1 (2024-11-06) + + * [BC BREAK] Fix a security issue in the sandbox mode allowing an attacker to call attributes on Array-like objects + They are now checked via the property policy + * Fix a security issue in the sandbox mode allowing an attacker to be able to call `toString()` + under some circumstances on an object even if the `__toString()` method is not allowed by the security policy + # 3.14.0 (2024-09-09) * Fix a security issue when an included sandboxed template has been loaded before without the sandbox context @@ -44,6 +347,17 @@ * Fix integration tests when a test has more than one data/expect section and deprecations * Add the `enum_cases` function +# 3.11.2 (2024-11-06) + + * [BC BREAK] Fix a security issue in the sandbox mode allowing an attacker to call attributes on Array-like objects + They are now checked via the property policy + * Fix a security issue in the sandbox mode allowing an attacker to be able to call `toString()` + under some circumstances on an object even if the `__toString()` method is not allowed by the security policy + +# 3.11.1 (2024-09-10) + + * Fix a security issue when an included sandboxed template has been loaded before without the sandbox context + # 3.11.0 (2024-08-08) * Deprecate `OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER` diff --git a/data/web/inc/lib/vendor/twig/twig/composer.json b/data/web/inc/lib/vendor/twig/twig/composer.json index e0c3e6c6c..788ebe708 100644 --- a/data/web/inc/lib/vendor/twig/twig/composer.json +++ b/data/web/inc/lib/vendor/twig/twig/composer.json @@ -24,15 +24,16 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php81": "^1.29" + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0", - "psr/container": "^1.0|^2.0" + "psr/container": "^1.0|^2.0", + "phpstan/phpstan": "^2.0@stable", + "php-cs-fixer/shim": "^3.0@stable" }, "autoload": { "files": [ diff --git a/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php b/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php index f67184300..4ea9f6695 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php +++ b/data/web/inc/lib/vendor/twig/twig/src/AbstractTwigCallable.php @@ -32,11 +32,38 @@ abstract class AbstractTwigCallable implements TwigCallableInterface 'needs_environment' => false, 'needs_context' => false, 'needs_charset' => false, + 'needs_is_sandboxed' => false, 'is_variadic' => false, + 'always_allowed_in_sandbox' => false, + 'deprecation_info' => null, 'deprecated' => false, 'deprecating_package' => '', 'alternative' => null, ], $options); + + if ($this->options['deprecation_info'] && !$this->options['deprecation_info'] instanceof DeprecatedCallableInfo) { + throw new \LogicException(\sprintf('The "deprecation_info" option must be an instance of "%s".', DeprecatedCallableInfo::class)); + } + + if ($this->options['deprecated']) { + if ($this->options['deprecation_info']) { + throw new \LogicException('When setting the "deprecation_info" option, you need to remove the obsolete deprecated options.'); + } + + trigger_deprecation('twig/twig', '3.15', 'Using the "deprecated", "deprecating_package", and "alternative" options is deprecated, pass a "deprecation_info" one instead.'); + + $this->options['deprecation_info'] = new DeprecatedCallableInfo( + $this->options['deprecating_package'], + $this->options['deprecated'], + null, + $this->options['alternative'], + ); + } + + if ($this->options['deprecation_info']) { + $this->options['deprecation_info']->setName($name); + $this->options['deprecation_info']->setType($this->getType()); + } } public function __toString(): string @@ -54,6 +81,9 @@ abstract class AbstractTwigCallable implements TwigCallableInterface return $this->dynamicName; } + /** + * @return callable|array{class-string, string}|null + */ public function getCallable() { return $this->callable; @@ -79,6 +109,19 @@ abstract class AbstractTwigCallable implements TwigCallableInterface return $this->options['needs_context']; } + public function needsIsSandboxed(): bool + { + return $this->options['needs_is_sandboxed']; + } + + public function isAlwaysAllowedInSandbox(): bool + { + return $this->options['always_allowed_in_sandbox']; + } + + /** + * @return static + */ public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self { $new = clone $this; @@ -111,26 +154,46 @@ abstract class AbstractTwigCallable implements TwigCallableInterface public function isDeprecated(): bool { - return (bool) $this->options['deprecated']; + return (bool) $this->options['deprecation_info']; } + public function triggerDeprecation(?string $file = null, ?int $line = null): void + { + $this->options['deprecation_info']->triggerDeprecation($file, $line); + } + + /** + * @deprecated since Twig 3.15 + */ public function getDeprecatingPackage(): string { + trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class); + return $this->options['deprecating_package']; } + /** + * @deprecated since Twig 3.15 + */ public function getDeprecatedVersion(): string { + trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class); + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; } + /** + * @deprecated since Twig 3.15 + */ public function getAlternative(): ?string { + trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class); + return $this->options['alternative']; } public function getMinimalNumberOfRequiredArguments(): int { - return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + \count($this->arguments); + return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + ($this->options['needs_is_sandboxed'] ? 1 : 0) + \count($this->arguments); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Attribute/AsTwigFilter.php b/data/web/inc/lib/vendor/twig/twig/src/Attribute/AsTwigFilter.php new file mode 100644 index 000000000..5689c9dfb --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Attribute/AsTwigFilter.php @@ -0,0 +1,60 @@ + */ + private array $templates = []; + + /** @var list */ + private array $lineage = []; + + /** @var array */ + private array $blocks = []; + + /** + * Whether the lineage can no longer move, making every further resolution pointless. + */ + private bool $fixed = false; + + /** + * @param iterable $templates Templates and nested chains ordered from highest to lowest precedence + * @param array $context Default variables used to resolve dynamic parent expressions + */ + public function __construct( + private Environment $env, + iterable $templates, + private array $context = [], + ) { + foreach ($templates as $template) { + if ($template instanceof self) { + if ($env !== $template->env) { + throw new \LogicException('A block chain cannot contain a block chain from a different Twig environment.'); + } + + $this->templates[] = $template; + continue; + } + + if (\is_string($template)) { + $template = $env->load($template); + } + if (!$template instanceof TemplateWrapper) { + throw new \TypeError(\sprintf('Block chain templates must be strings, "%s" or "%s" instances, "%s" given.', TemplateWrapper::class, self::class, get_debug_type($template))); + } + + if (!$template->isOwnedBy($env)) { + throw new \LogicException('A block chain cannot contain templates from different Twig environments.'); + } + + $this->templates[] = $template->unwrap($env); + } + + if (!$this->templates) { + throw new \InvalidArgumentException('A block chain requires at least one template.'); + } + } + + /** + * @param array $context + */ + public function hasBlock(string $name, array $context = []): bool + { + $blocks = $this->fixed ? $this->blocks : $this->resolveBlocks($context + $this->context + $this->env->getGlobals()); + + return isset($blocks[$name]); + } + + /** + * @param array $context + * + * @return string[] + */ + public function getBlockNames(array $context = []): array + { + return array_keys($this->fixed ? $this->blocks : $this->resolveBlocks($context + $this->context + $this->env->getGlobals())); + } + + /** + * @param array $context + * + * @return iterable + */ + public function streamBlock(string $name, array $context = []): iterable + { + $context += $this->context + $this->env->getGlobals(); + $blocks = $this->fixed ? $this->blocks : $this->resolveBlocks($context); + if (!isset($blocks[$name])) { + $this->throwUnknownBlock($name); + } + + yield from $this->entryTemplate()->yieldBlock($name, $context, $blocks); + } + + /** + * @param array $context + */ + public function renderBlock(string $name, array $context = []): string + { + $context += $this->context + $this->env->getGlobals(); + $blocks = $this->fixed ? $this->blocks : $this->resolveBlocks($context); + if (!isset($blocks[$name])) { + $this->throwUnknownBlock($name); + } + + return $this->entryTemplate()->renderBlock($name, $context, $blocks); + } + + /** + * @param array $context + */ + public function displayBlock(string $name, array $context = []): void + { + $context += $this->context + $this->env->getGlobals(); + $blocks = $this->fixed ? $this->blocks : $this->resolveBlocks($context); + if (!isset($blocks[$name])) { + $this->throwUnknownBlock($name); + } + + $this->entryTemplate()->displayBlock($name, $context, $blocks); + } + + /** + * @param array $context + * + * @return array + */ + private function resolveBlocks(array $context): array + { + [$lineage, $fixed] = $this->resolveLineage($context); + + $stale = $lineage !== $this->lineage; + + foreach ($lineage as $template) { + if (!$template instanceof self) { + continue; + } + + if (!$template->fixed) { + // a nested chain that can still move may resolve to other blocks on every call + $template->resolveBlocks($context + $template->context); + $stale = true; + } + + $fixed = $fixed && $template->fixed; + } + + $this->fixed = $fixed; + + if (!$stale) { + return $this->blocks; + } + + $blocks = []; + foreach ($lineage as $template) { + if ($template instanceof self) { + $blocks += $template->blocks; + continue; + } + + foreach ($template->getBlocks() as $name => $block) { + $blocks[$name] ??= $block; + } + } + + $this->lineage = $lineage; + + return $this->blocks = $blocks; + } + + /** + * @param array $context + * + * @return array{list, bool} + */ + private function resolveLineage(array $context): array + { + $lineage = []; + $fixed = true; + + foreach ($this->templates as $template) { + if ($template instanceof self) { + $lineage[] = $template; + continue; + } + + $seen = []; + do { + if (isset($seen[$id = spl_object_id($template)])) { + throw new \LogicException(\sprintf('Circular template inheritance detected while building a block chain from "%s".', $template->getTemplateName())); + } + $seen[$id] = true; + $lineage[] = $template; + + $parent = $template->getParent($context); + $fixed = $fixed && $template->hasFixedParent(); + + $template = $parent; + } while (false !== $template); + } + + return [$lineage, $fixed]; + } + + private function entryTemplate(): Template + { + return ($template = $this->templates[0]) instanceof self ? $template->entryTemplate() : $template; + } + + private function throwUnknownBlock(string $name): never + { + throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.', $name, $this->entryTemplate()->getTemplateName()), -1, $this->entryTemplate()->getSourceContext()); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Cache/ChainCache.php b/data/web/inc/lib/vendor/twig/twig/src/Cache/ChainCache.php index c94afdb43..1c2098f1f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Cache/ChainCache.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Cache/ChainCache.php @@ -19,7 +19,7 @@ namespace Twig\Cache; * * @author Quentin Devos */ -final class ChainCache implements CacheInterface +final class ChainCache implements CacheInterface, RemovableCacheInterface { /** * @param iterable $caches The ordered list of caches used to store and fetch cached items @@ -69,6 +69,15 @@ final class ChainCache implements CacheInterface return 0; } + public function remove(string $name, string $cls): void + { + foreach ($this->caches as $cache) { + if ($cache instanceof RemovableCacheInterface) { + $cache->remove($name, $cls); + } + } + } + /** * @return string[] */ diff --git a/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php b/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php index 2e79fac05..5840585e3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Cache/FilesystemCache.php @@ -16,7 +16,7 @@ namespace Twig\Cache; * * @author Andrew Tch */ -class FilesystemCache implements CacheInterface +class FilesystemCache implements CacheInterface, RemovableCacheInterface { public const FORCE_BYTECODE_INVALIDATION = 1; @@ -76,6 +76,14 @@ class FilesystemCache implements CacheInterface throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key)); } + public function remove(string $name, string $cls): void + { + $key = $this->generateKey($name, $cls); + if (!@unlink($key) && file_exists($key)) { + throw new \RuntimeException(\sprintf('Failed to delete cache file "%s".', $key)); + } + } + public function getTimestamp(string $key): int { if (!is_file($key)) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Cache/NullCache.php b/data/web/inc/lib/vendor/twig/twig/src/Cache/NullCache.php index 8d20d59d8..1ae216928 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Cache/NullCache.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Cache/NullCache.php @@ -16,7 +16,7 @@ namespace Twig\Cache; * * @author Fabien Potencier */ -final class NullCache implements CacheInterface +final class NullCache implements CacheInterface, RemovableCacheInterface { public function generateKey(string $name, string $className): string { @@ -35,4 +35,8 @@ final class NullCache implements CacheInterface { return 0; } + + public function remove(string $name, string $cls): void + { + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Cache/RemovableCacheInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Cache/RemovableCacheInterface.php new file mode 100644 index 000000000..05da56913 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Cache/RemovableCacheInterface.php @@ -0,0 +1,20 @@ + + */ +interface RemovableCacheInterface +{ + public function remove(string $name, string $cls): void; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Compiler.php b/data/web/inc/lib/vendor/twig/twig/src/Compiler.php index 1a43aa7f6..976030f79 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Compiler.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Compiler.php @@ -74,7 +74,7 @@ class Compiler $node->compile($this); if ($this->didUseEcho) { - trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node)); + trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, $node::class); } return $this; @@ -99,7 +99,7 @@ class Compiler $node->compile($this); if ($this->didUseEcho) { - trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[YieldReady].', $this->didUseEcho, \get_class($node)); + trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, $node::class); } return $this; @@ -143,7 +143,13 @@ class Compiler */ public function string(string $value) { - $this->source .= \sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + // Single quotes are encoded as \x27 (not \') as a defense-in-depth measure: + // it guarantees that the compiled output never contains a literal "'" derived + // from user input, which prevents breaking out of a surrounding single-quoted + // PHP context if a caller mistakenly concatenates the result into one. + // \' is not a recognized escape sequence in PHP double-quoted strings (the + // backslash would be kept literally), so \x27 is used instead. + $this->source .= \sprintf('"%s"', str_replace("'", '\\x27', addcslashes($value, "\0\t\"\$\\"))); return $this; } @@ -170,7 +176,7 @@ class Compiler } elseif (\is_bool($value)) { $this->raw($value ? 'true' : 'false'); } elseif (\is_array($value)) { - $this->raw('array('); + $this->raw('['); $first = true; foreach ($value as $key => $v) { if (!$first) { @@ -181,7 +187,7 @@ class Compiler $this->raw(' => '); $this->repr($v); } - $this->raw(')'); + $this->raw(']'); } else { $this->string($value); } @@ -243,7 +249,7 @@ class Compiler public function getVarName(): string { - return \sprintf('__internal_compile_%d', $this->varNameSalt++); + return \sprintf('_v%d', $this->varNameSalt++); } private function checkForEcho(string $string): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/DeprecatedCallableInfo.php b/data/web/inc/lib/vendor/twig/twig/src/DeprecatedCallableInfo.php new file mode 100644 index 000000000..2db9f3d28 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/DeprecatedCallableInfo.php @@ -0,0 +1,67 @@ + + */ +final class DeprecatedCallableInfo +{ + private string $type; + private string $name; + + public function __construct( + private string $package, + private string $version, + private ?string $altName = null, + private ?string $altPackage = null, + private ?string $altVersion = null, + ) { + } + + public function setType(string $type): void + { + $this->type = $type; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function triggerDeprecation(?string $file = null, ?int $line = null): void + { + $message = \sprintf('Twig %s "%s" is deprecated', ucfirst($this->type), $this->name); + + if ($this->altName) { + $message .= \sprintf('; use "%s"', $this->altName); + if ($this->altPackage) { + $message .= \sprintf(' from the "%s" package', $this->altPackage); + } + if ($this->altVersion) { + $message .= \sprintf(' (available since version %s)', $this->altVersion); + } + $message .= ' instead'; + } + + if ($file) { + $message .= \sprintf(' in %s', $file); + if ($line) { + $message .= \sprintf(' at line %d', $line); + } + } + + $message .= '.'; + + trigger_deprecation($this->package, $this->version, $message); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Environment.php b/data/web/inc/lib/vendor/twig/twig/src/Environment.php index 24e55e979..7062ecb14 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Environment.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Environment.php @@ -14,10 +14,12 @@ namespace Twig; use Twig\Cache\CacheInterface; use Twig\Cache\FilesystemCache; use Twig\Cache\NullCache; +use Twig\Cache\RemovableCacheInterface; use Twig\Error\Error; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; +use Twig\ExpressionParser\ExpressionParsers; use Twig\Extension\CoreExtension; use Twig\Extension\EscaperExtension; use Twig\Extension\ExtensionInterface; @@ -26,8 +28,6 @@ use Twig\Extension\YieldNotReadyExtension; use Twig\Loader\ArrayLoader; use Twig\Loader\ChainLoader; use Twig\Loader\LoaderInterface; -use Twig\Node\Expression\Binary\AbstractBinary; -use Twig\Node\Expression\Unary\AbstractUnary; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\NodeVisitor\NodeVisitorInterface; @@ -43,10 +43,10 @@ use Twig\TokenParser\TokenParserInterface; */ class Environment { - public const VERSION = '3.14.0'; - public const VERSION_ID = 31400; + public const VERSION = '3.30.0'; + public const VERSION_ID = 33000; public const MAJOR_VERSION = 3; - public const MINOR_VERSION = 14; + public const MINOR_VERSION = 30; public const RELEASE_VERSION = 0; public const EXTRA_VERSION = ''; @@ -71,6 +71,11 @@ class Environment /** @var bool */ private $useYield; private $defaultRuntimeLoader; + private array $hotCache = []; + /** + * @var array + */ + private array $loadedWrappers = []; /** * Constructor. @@ -143,6 +148,11 @@ class Environment $this->addExtension(new OptimizerExtension($options['optimizations'])); } + public function __clone() + { + trigger_deprecation('twig/twig', '3.30', 'Cloning a "%s" instance is deprecated and will throw in Twig 4.0; build a new environment instead.', self::class); + } + /** * @internal */ @@ -153,20 +163,24 @@ class Environment /** * Enables debugging mode. + * + * @return void */ public function enableDebug() { $this->debug = true; - $this->updateOptionsHash(); + $this->optionsHash = null; } /** * Disables debugging mode. + * + * @return void */ public function disableDebug() { $this->debug = false; - $this->updateOptionsHash(); + $this->optionsHash = null; } /** @@ -181,6 +195,8 @@ class Environment /** * Enables the auto_reload option. + * + * @return void */ public function enableAutoReload() { @@ -189,6 +205,8 @@ class Environment /** * Disables the auto_reload option. + * + * @return void */ public function disableAutoReload() { @@ -207,20 +225,24 @@ class Environment /** * Enables the strict_variables option. + * + * @return void */ public function enableStrictVariables() { $this->strictVariables = true; - $this->updateOptionsHash(); + $this->optionsHash = null; } /** * Disables the strict_variables option. + * + * @return void */ public function disableStrictVariables() { $this->strictVariables = false; - $this->updateOptionsHash(); + $this->optionsHash = null; } /** @@ -233,6 +255,19 @@ class Environment return $this->strictVariables; } + public function removeCache(string $name): void + { + $cls = $this->getTemplateClass($name); + $this->hotCache[$name] = $cls.'_'.bin2hex(random_bytes(16)); + unset($this->loadedWrappers[$cls]); + + if ($this->cache instanceof RemovableCacheInterface) { + $this->cache->remove($name, $cls); + } else { + throw new \LogicException(\sprintf('The "%s" cache class does not support removing template cache as it does not implement the "RemovableCacheInterface" interface.', \get_class($this->cache))); + } + } + /** * Gets the current cache implementation. * @@ -253,6 +288,8 @@ class Environment * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation, * an absolute path to the compiled templates, * or false to disable cache + * + * @return void */ public function setCache($cache) { @@ -287,7 +324,7 @@ class Environment */ public function getTemplateClass(string $name, ?int $index = null): string { - $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; + $key = ($this->hotCache[$name] ?? $this->getLoader()->getCacheKey($name)).($this->optionsHash ??= $this->getOptionsHash()); return '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); } @@ -332,6 +369,8 @@ class Environment public function load($name): TemplateWrapper { if ($name instanceof TemplateWrapper) { + $name->unwrap($this); + return $name; } if ($name instanceof Template) { @@ -340,7 +379,9 @@ class Environment return $name; } - return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + $cls = $this->getTemplateClass($name); + + return $this->loadedWrappers[$cls] ??= new TemplateWrapper($this, $this->loadTemplate($cls, $name)); } /** @@ -379,8 +420,10 @@ class Environment if (!class_exists($cls, false)) { $source = $this->getLoader()->getSourceContext($name); $content = $this->compileSource($source); - $this->cache->write($key, $content); - $this->cache->load($key); + if (!isset($this->hotCache[$name])) { + $this->cache->write($key, $content); + $this->cache->load($key); + } if (!class_exists($mainCls, false)) { /* Last line of defense if either $this->bcWriteCacheFile was used, @@ -474,7 +517,7 @@ class Environment return new TemplateWrapper($this, $name); } if ($name instanceof TemplateWrapper) { - return $name; + return $this->load($name); } if (1 !== $count && !$this->getLoader()->exists($name)) { @@ -487,6 +530,9 @@ class Environment throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); } + /** + * @return void + */ public function setLexer(Lexer $lexer) { $this->lexer = $lexer; @@ -504,6 +550,9 @@ class Environment return $this->lexer->tokenize($source); } + /** + * @return void + */ public function setParser(Parser $parser) { $this->parser = $parser; @@ -523,6 +572,9 @@ class Environment return $this->parser->parse($stream); } + /** + * @return void + */ public function setCompiler(Compiler $compiler) { $this->compiler = $compiler; @@ -557,6 +609,9 @@ class Environment } } + /** + * @return void + */ public function setLoader(LoaderInterface $loader) { $this->loader = $loader; @@ -567,6 +622,9 @@ class Environment return $this->loader; } + /** + * @return void + */ public function setCharset(string $charset) { if ('UTF8' === $charset = strtoupper($charset ?: '')) { @@ -587,6 +645,9 @@ class Environment return $this->extensionSet->hasExtension($class); } + /** + * @return void + */ public function addRuntimeLoader(RuntimeLoaderInterface $loader) { $this->runtimeLoaders[] = $loader; @@ -634,19 +695,24 @@ class Environment throw new RuntimeError(\sprintf('Unable to load the "%s" runtime.', $class)); } + /** + * @return void + */ public function addExtension(ExtensionInterface $extension) { $this->extensionSet->addExtension($extension); - $this->updateOptionsHash(); + $this->optionsHash = null; } /** * @param ExtensionInterface[] $extensions An array of extensions + * + * @return void */ public function setExtensions(array $extensions) { $this->extensionSet->setExtensions($extensions); - $this->updateOptionsHash(); + $this->optionsHash = null; } /** @@ -657,6 +723,9 @@ class Environment return $this->extensionSet->getExtensions(); } + /** + * @return void + */ public function addTokenParser(TokenParserInterface $parser) { $this->extensionSet->addTokenParser($parser); @@ -680,11 +749,17 @@ class Environment return $this->extensionSet->getTokenParser($name); } + /** + * @param callable(string): (TokenParserInterface|false) $callable + */ public function registerUndefinedTokenParserCallback(callable $callable): void { $this->extensionSet->registerUndefinedTokenParserCallback($callable); } + /** + * @return void + */ public function addNodeVisitor(NodeVisitorInterface $visitor) { $this->extensionSet->addNodeVisitor($visitor); @@ -700,6 +775,9 @@ class Environment return $this->extensionSet->getNodeVisitors(); } + /** + * @return void + */ public function addFilter(TwigFilter $filter) { $this->extensionSet->addFilter($filter); @@ -713,6 +791,9 @@ class Environment return $this->extensionSet->getFilter($name); } + /** + * @param callable(string): (TwigFilter|false) $callable + */ public function registerUndefinedFilterCallback(callable $callable): void { $this->extensionSet->registerUndefinedFilterCallback($callable); @@ -734,6 +815,9 @@ class Environment return $this->extensionSet->getFilters(); } + /** + * @return void + */ public function addTest(TwigTest $test) { $this->extensionSet->addTest($test); @@ -757,6 +841,17 @@ class Environment return $this->extensionSet->getTest($name); } + /** + * @param callable(string): (TwigTest|false) $callable + */ + public function registerUndefinedTestCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedTestCallback($callable); + } + + /** + * @return void + */ public function addFunction(TwigFunction $function) { $this->extensionSet->addFunction($function); @@ -770,6 +865,9 @@ class Environment return $this->extensionSet->getFunction($name); } + /** + * @param callable(string): (TwigFunction|false) $callable + */ public function registerUndefinedFunctionCallback(callable $callable): void { $this->extensionSet->registerUndefinedFunctionCallback($callable); @@ -798,6 +896,8 @@ class Environment * but after, you can only update existing globals. * * @param mixed $value The global value + * + * @return void */ public function addGlobal(string $name, $value) { @@ -813,8 +913,6 @@ class Environment } /** - * @internal - * * @return array */ public function getGlobals(): array @@ -848,27 +946,15 @@ class Environment /** * @internal - * - * @return array}> */ - public function getUnaryOperators(): array + public function getExpressionParsers(): ExpressionParsers { - return $this->extensionSet->getUnaryOperators(); + return $this->extensionSet->getExpressionParsers(); } - /** - * @internal - * - * @return array, associativity: ExpressionParser::OPERATOR_*}> - */ - public function getBinaryOperators(): array + private function getOptionsHash(): string { - return $this->extensionSet->getBinaryOperators(); - } - - private function updateOptionsHash(): void - { - $this->optionsHash = implode(':', [ + return implode(':', [ $this->extensionSet->getSignature(), \PHP_MAJOR_VERSION, \PHP_MINOR_VERSION, diff --git a/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php b/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php index 61c309fa1..8494d243f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Error/Error.php @@ -29,44 +29,39 @@ use Twig\Template; * Whenever possible, you must set these information (original template name * and line number) yourself by passing them to the constructor. If some or all * these information are not available from where you throw the exception, then - * this class will guess them automatically (when the line number is set to -1 - * and/or the name is set to null). As this is a costly operation, this - * can be disabled by passing false for both the name and the line number - * when creating a new instance of this class. + * this class will guess them automatically. * * @author Fabien Potencier */ class Error extends \Exception { private $lineno; - private $name; + /** @var positive-int|null */ + private ?int $columnno; private $rawMessage; - private $sourcePath; - private $sourceCode; + private ?Source $source; + private string $phpFile; + private int $phpLine; /** * Constructor. * * By default, automatic guessing is enabled. * - * @param string $message The error message - * @param int $lineno The template line where the error occurred - * @param Source|null $source The source context where the error occurred + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Source|null $source The source context where the error occurred + * @param positive-int|null $columnno The template column where the error occurred */ - public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\Throwable $previous = null) + public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\Throwable $previous = null, ?int $columnno = null) { parent::__construct('', 0, $previous); - if (null === $source) { - $name = null; - } else { - $name = $source->getName(); - $this->sourceCode = $source->getCode(); - $this->sourcePath = $source->getPath(); - } - + $this->phpFile = $this->getFile(); + $this->phpLine = $this->getLine(); $this->lineno = $lineno; - $this->name = $name; + $this->columnno = $columnno; + $this->source = $source; $this->rawMessage = $message; $this->updateRepr(); } @@ -84,30 +79,45 @@ class Error extends \Exception public function setTemplateLine(int $lineno): void { $this->lineno = $lineno; + $this->updateRepr(); + } + /** + * Returns the 1-based column where the error occurred, or null if unknown. + * + * @return positive-int|null + */ + public function getTemplateColumn(): ?int + { + return $this->columnno; + } + + /** + * @param positive-int|null $columnno + */ + public function setTemplateColumn(?int $columnno): void + { + $this->columnno = $columnno; $this->updateRepr(); } public function getSourceContext(): ?Source { - return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; + return $this->source; } public function setSourceContext(?Source $source = null): void { - if (null === $source) { - $this->sourceCode = $this->name = $this->sourcePath = null; - } else { - $this->sourceCode = $source->getCode(); - $this->name = $source->getName(); - $this->sourcePath = $source->getPath(); - } - + $this->source = $source; $this->updateRepr(); } public function guess(): void { + if ($this->lineno > -1) { + return; + } + $this->guessTemplateInfo(); $this->updateRepr(); } @@ -120,80 +130,52 @@ class Error extends \Exception private function updateRepr(): void { - $this->message = $this->rawMessage; - - if ($this->sourcePath && $this->lineno > 0) { - $this->file = $this->sourcePath; - $this->line = $this->lineno; - - return; - } - - $dot = false; - if (str_ends_with($this->message, '.')) { - $this->message = substr($this->message, 0, -1); - $dot = true; - } - - $questionMark = false; - if (str_ends_with($this->message, '?')) { - $this->message = substr($this->message, 0, -1); - $questionMark = true; - } - - if ($this->name) { - if (\is_string($this->name) || $this->name instanceof \Stringable) { - $name = \sprintf('"%s"', $this->name); + if ($this->source && $this->source->getPath()) { + // we only update the file and the line together + $this->file = $this->source->getPath(); + if ($this->lineno > 0) { + $this->line = $this->lineno; } else { - $name = json_encode($this->name); + $this->line = -1; } - $this->message .= \sprintf(' in %s', $name); } - if ($this->lineno && $this->lineno >= 0) { + $this->message = $this->rawMessage; + $last = substr($this->message, -1); + if ($punctuation = '.' === $last || '?' === $last ? $last : '') { + $this->message = substr($this->message, 0, -1); + } + if ($this->source && $this->source->getName()) { + $this->message .= \sprintf(' in "%s"', $this->source->getName()); + } + if ($this->lineno > 0) { $this->message .= \sprintf(' at line %d', $this->lineno); + if (null !== $this->columnno) { + $this->message .= \sprintf(' column %d', $this->columnno); + } } - - if ($dot) { - $this->message .= '.'; - } - - if ($questionMark) { - $this->message .= '?'; + if ($punctuation) { + $this->message .= $punctuation; } } private function guessTemplateInfo(): void { - $template = null; - $templateClass = null; + // $this->source is never null here (see guess() usage in Template) + $this->lineno = 0; + $template = null; $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); foreach ($backtrace as $trace) { - if (isset($trace['object']) && $trace['object'] instanceof Template) { - $currentClass = \get_class($trace['object']); - $isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass); - if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { - $template = $trace['object']; - $templateClass = \get_class($trace['object']); - } + if (isset($trace['object']) && $trace['object'] instanceof Template && $this->source->getName() === $trace['object']->getTemplateName()) { + $template = $trace['object']; + + break; } } - // update template name - if (null !== $template && null === $this->name) { - $this->name = $template->getTemplateName(); - } - - // update template path if any - if (null !== $template && null === $this->sourcePath) { - $src = $template->getSourceContext(); - $this->sourceCode = $src->getCode(); - $this->sourcePath = $src->getPath(); - } - - if (null === $template || $this->lineno > -1) { - return; + if (null === $template) { + return; // Impossible to guess the info as the template was not found in the backtrace } $r = new \ReflectionObject($template); @@ -206,8 +188,7 @@ class Error extends \Exception while ($e = array_pop($exceptions)) { $traces = $e->getTrace(); - array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); - + array_unshift($traces, ['file' => $e instanceof self ? $e->phpFile : $e->getFile(), 'line' => $e instanceof self ? $e->phpLine : $e->getLine()]); while ($trace = array_shift($traces)) { if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { continue; diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php index dc4a6015d..3ba94d076 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser.php @@ -12,25 +12,19 @@ namespace Twig; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Error\SyntaxError; -use Twig\Node\Expression\AbstractExpression; +use Twig\ExpressionParser\Infix\DotExpressionParser; +use Twig\ExpressionParser\Infix\FilterExpressionParser; +use Twig\ExpressionParser\Infix\SquareBracketExpressionParser; use Twig\Node\Expression\ArrayExpression; -use Twig\Node\Expression\ArrowFunctionExpression; -use Twig\Node\Expression\AssignNameExpression; -use Twig\Node\Expression\Binary\AbstractBinary; -use Twig\Node\Expression\Binary\ConcatBinary; -use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\MethodCallExpression; -use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\TestExpression; -use Twig\Node\Expression\Unary\AbstractUnary; use Twig\Node\Expression\Unary\NegUnary; -use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; +use Twig\Node\Expression\Unary\SpreadUnary; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; +use Twig\Node\Nodes; /** * Parses expressions. @@ -41,400 +35,108 @@ use Twig\Node\Node; * @see https://en.wikipedia.org/wiki/Operator-precedence_parser * * @author Fabien Potencier + * + * @deprecated since Twig 3.21 */ class ExpressionParser { + /** + * @deprecated since Twig 3.21 + */ public const OPERATOR_LEFT = 1; + /** + * @deprecated since Twig 3.21 + */ public const OPERATOR_RIGHT = 2; - /** @var array}> */ - private $unaryOperators; - /** @var array, associativity: self::OPERATOR_*}> */ - private $binaryOperators; - private $readyNodes = []; - public function __construct( private Parser $parser, private Environment $env, ) { - $this->unaryOperators = $env->getUnaryOperators(); - $this->binaryOperators = $env->getBinaryOperators(); + trigger_deprecation('twig/twig', '3.21', 'Class "%s" is deprecated, use "Parser::parseExpression()" instead.', __CLASS__); } - public function parseExpression($precedence = 0, $allowArrow = false) + public function parseExpression($precedence = 0) { - if ($allowArrow && $arrow = $this->parseArrow()) { - return $arrow; + if (\func_num_args() > 1) { + trigger_deprecation('twig/twig', '3.15', 'Passing a second argument ($allowArrow) to "%s()" is deprecated.', __METHOD__); } - $expr = $this->getPrimary(); - $token = $this->parser->getCurrentToken(); - while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { - $op = $this->binaryOperators[$token->getValue()]; - $this->parser->getStream()->next(); + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated, use "Parser::parseExpression()" instead.', __METHOD__); - if ('is not' === $token->getValue()) { - $expr = $this->parseNotTestExpression($expr); - } elseif ('is' === $token->getValue()) { - $expr = $this->parseTestExpression($expr); - } elseif (isset($op['callable'])) { - $expr = $op['callable']($this->parser, $expr); - } else { - $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], true); - $class = $op['class']; - $expr = new $class($expr, $expr1, $token->getLine()); - } - - $token = $this->parser->getCurrentToken(); - } - - if (0 === $precedence) { - return $this->parseConditionalExpression($expr); - } - - return $expr; + return $this->parser->parseExpression((int) $precedence); } /** - * @return ArrowFunctionExpression|null + * @deprecated since Twig 3.21 */ - private function parseArrow() - { - $stream = $this->parser->getStream(); - - // short array syntax (one argument, no parentheses)? - if ($stream->look(1)->test(Token::ARROW_TYPE)) { - $line = $stream->getCurrent()->getLine(); - $token = $stream->expect(Token::NAME_TYPE); - $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; - $stream->expect(Token::ARROW_TYPE); - - return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); - } - - // first, determine if we are parsing an arrow function by finding => (long form) - $i = 0; - if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) { - return null; - } - ++$i; - while (true) { - // variable name - ++$i; - if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) { - break; - } - ++$i; - } - if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) { - return null; - } - ++$i; - if (!$stream->look($i)->test(Token::ARROW_TYPE)) { - return null; - } - - // yes, let's parse it properly - $token = $stream->expect(Token::PUNCTUATION_TYPE, '('); - $line = $token->getLine(); - - $names = []; - while (true) { - $token = $stream->expect(Token::NAME_TYPE); - $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); - - if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { - break; - } - } - $stream->expect(Token::PUNCTUATION_TYPE, ')'); - $stream->expect(Token::ARROW_TYPE); - - return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); - } - - private function getPrimary(): AbstractExpression - { - $token = $this->parser->getCurrentToken(); - - if ($this->isUnary($token)) { - $operator = $this->unaryOperators[$token->getValue()]; - $this->parser->getStream()->next(); - $expr = $this->parseExpression($operator['precedence']); - $class = $operator['class']; - - return $this->parsePostfixExpression(new $class($expr, $token->getLine())); - } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) { - $this->parser->getStream()->next(); - $expr = $this->parseExpression(); - $this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); - - return $this->parsePostfixExpression($expr); - } - - return $this->parsePrimaryExpression(); - } - - private function parseConditionalExpression($expr): AbstractExpression - { - while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) { - if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { - $expr2 = $this->parseExpression(); - if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { - // Ternary operator (expr ? expr2 : expr3) - $expr3 = $this->parseExpression(); - } else { - // Ternary without else (expr ? expr2) - $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); - } - } else { - // Ternary without then (expr ?: expr3) - $expr2 = $expr; - $expr3 = $this->parseExpression(); - } - - $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); - } - - return $expr; - } - - private function isUnary(Token $token): bool - { - return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); - } - - private function isBinary(Token $token): bool - { - return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); - } - public function parsePrimaryExpression() { - $token = $this->parser->getCurrentToken(); - switch ($token->getType()) { - case Token::NAME_TYPE: - $this->parser->getStream()->next(); - switch ($token->getValue()) { - case 'true': - case 'TRUE': - $node = new ConstantExpression(true, $token->getLine()); - break; + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); - case 'false': - case 'FALSE': - $node = new ConstantExpression(false, $token->getLine()); - break; - - case 'none': - case 'NONE': - case 'null': - case 'NULL': - $node = new ConstantExpression(null, $token->getLine()); - break; - - default: - if ('(' === $this->parser->getCurrentToken()->getValue()) { - $node = $this->getFunctionNode($token->getValue(), $token->getLine()); - } else { - $node = new NameExpression($token->getValue(), $token->getLine()); - } - } - break; - - case Token::NUMBER_TYPE: - $this->parser->getStream()->next(); - $node = new ConstantExpression($token->getValue(), $token->getLine()); - break; - - case Token::STRING_TYPE: - case Token::INTERPOLATION_START_TYPE: - $node = $this->parseStringExpression(); - break; - - case Token::OPERATOR_TYPE: - if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { - // in this context, string operators are variable names - $this->parser->getStream()->next(); - $node = new NameExpression($token->getValue(), $token->getLine()); - break; - } - - if (isset($this->unaryOperators[$token->getValue()])) { - $class = $this->unaryOperators[$token->getValue()]['class']; - if (!\in_array($class, [NegUnary::class, PosUnary::class])) { - throw new SyntaxError(\sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); - } - - $this->parser->getStream()->next(); - $expr = $this->parsePrimaryExpression(); - - $node = new $class($expr, $token->getLine()); - break; - } - - // no break - default: - if ($token->test(Token::PUNCTUATION_TYPE, '[')) { - $node = $this->parseSequenceExpression(); - } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { - $node = $this->parseMappingExpression(); - } elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { - throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); - } else { - throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); - } - } - - return $this->parsePostfixExpression($node); - } - - public function parseStringExpression() - { - $stream = $this->parser->getStream(); - - $nodes = []; - // a string cannot be followed by another string in a single expression - $nextCanBeString = true; - while (true) { - if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { - $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); - $nextCanBeString = false; - } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { - $nodes[] = $this->parseExpression(); - $stream->expect(Token::INTERPOLATION_END_TYPE); - $nextCanBeString = true; - } else { - break; - } - } - - $expr = array_shift($nodes); - foreach ($nodes as $node) { - $expr = new ConcatBinary($expr, $node, $node->getTemplateLine()); - } - - return $expr; + return $this->parseExpression(); } /** - * @deprecated since 3.11, use parseSequenceExpression() instead + * @deprecated since Twig 3.21 + */ + public function parseStringExpression() + { + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); + + return $this->parseExpression(); + } + + /** + * @deprecated since Twig 3.11, use parseExpression() instead */ public function parseArrayExpression() { - trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__); + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseExpression()" instead.', __METHOD__); - return $this->parseSequenceExpression(); - } - - public function parseSequenceExpression() - { - $stream = $this->parser->getStream(); - $stream->expect(Token::PUNCTUATION_TYPE, '[', 'A sequence element was expected'); - - $node = new ArrayExpression([], $stream->getCurrent()->getLine()); - $first = true; - while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { - if (!$first) { - $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma'); - - // trailing ,? - if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { - break; - } - } - $first = false; - - if ($stream->test(Token::SPREAD_TYPE)) { - $stream->next(); - $expr = $this->parseExpression(); - $expr->setAttribute('spread', true); - $node->addElement($expr); - } else { - $node->addElement($this->parseExpression()); - } - } - $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed'); - - return $node; + return $this->parseExpression(); } /** - * @deprecated since 3.11, use parseMappingExpression() instead + * @deprecated since Twig 3.21 + */ + public function parseSequenceExpression() + { + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); + + return $this->parseExpression(); + } + + /** + * @deprecated since Twig 3.11, use parseExpression() instead */ public function parseHashExpression() { - trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__); + trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseExpression()" instead.', __METHOD__); - return $this->parseMappingExpression(); + return $this->parseExpression(); } + /** + * @deprecated since Twig 3.21 + */ public function parseMappingExpression() { - $stream = $this->parser->getStream(); - $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); - $node = new ArrayExpression([], $stream->getCurrent()->getLine()); - $first = true; - while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { - if (!$first) { - $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma'); - - // trailing ,? - if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { - break; - } - } - $first = false; - - if ($stream->test(Token::SPREAD_TYPE)) { - $stream->next(); - $value = $this->parseExpression(); - $value->setAttribute('spread', true); - $node->addElement($value); - continue; - } - - // a mapping key can be: - // - // * a number -- 12 - // * a string -- 'a' - // * a name, which is equivalent to a string -- a - // * an expression, which must be enclosed in parentheses -- (1 + 2) - if ($token = $stream->nextIf(Token::NAME_TYPE)) { - $key = new ConstantExpression($token->getValue(), $token->getLine()); - - // {a} is a shortcut for {a:a} - if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) { - $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine()); - $node->addElement($value, $key); - continue; - } - } elseif (($token = $stream->nextIf(Token::STRING_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { - $key = new ConstantExpression($token->getValue(), $token->getLine()); - } elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) { - $key = $this->parseExpression(); - } else { - $current = $stream->getCurrent(); - - throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); - } - - $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)'); - $value = $this->parseExpression(); - - $node->addElement($value, $key); - } - $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); - - return $node; + return $this->parseExpression(); } + /** + * @deprecated since Twig 3.21 + */ public function parsePostfixExpression($node) { + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); + while (true) { $token = $this->parser->getCurrentToken(); - if (Token::PUNCTUATION_TYPE == $token->getType()) { + if ($token->test(Token::PUNCTUATION_TYPE)) { if ('.' == $token->getValue() || '[' == $token->getValue()) { $node = $this->parseSubscriptExpression($node); } elseif ('|' == $token->getValue()) { @@ -450,154 +152,49 @@ class ExpressionParser return $node; } - public function getFunctionNode($name, $line) - { - if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { - $arguments = new ArrayExpression([], $line); - foreach ($this->parseArguments() as $n) { - $arguments->addElement($n); - } - - $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); - $node->setAttribute('safe', true); - - return $node; - } - - $args = $this->parseArguments(true); - $function = $this->getFunction($name, $line); - - if ($function->getParserCallable()) { - $fakeNode = new Node(lineno: $line); - $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext()); - - return ($function->getParserCallable())($this->parser, $fakeNode, $args, $line); - } - - if (!isset($this->readyNodes[$class = $function->getNodeClass()])) { - $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); - } - - if (!$ready = $this->readyNodes[$class]) { - trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFunction" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); - } - - return new $class($ready ? $function : $function->getName(), $args, $line); - } - + /** + * @deprecated since Twig 3.21 + */ public function parseSubscriptExpression($node) { - $stream = $this->parser->getStream(); - $token = $stream->next(); - $lineno = $token->getLine(); - $arguments = new ArrayExpression([], $lineno); - $type = Template::ANY_CALL; - if ('.' == $token->getValue()) { - $token = $stream->next(); - if ( - Token::NAME_TYPE == $token->getType() - || - Token::NUMBER_TYPE == $token->getType() - || - (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) - ) { - $arg = new ConstantExpression($token->getValue(), $lineno); + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); - if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { - $type = Template::METHOD_CALL; - foreach ($this->parseArguments() as $n) { - $arguments->addElement($n); - } - } - } else { - throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); - } + $parsers = new \ReflectionProperty($this->parser, 'parsers'); - if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { - $name = $arg->getAttribute('value'); - - $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); - $node->setAttribute('safe', true); - - return $node; - } - } else { - $type = Template::ARRAY_CALL; - - // slice? - $slice = false; - if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { - $slice = true; - $arg = new ConstantExpression(0, $token->getLine()); - } else { - $arg = $this->parseExpression(); - } - - if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { - $slice = true; - } - - if ($slice) { - if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { - $length = new ConstantExpression(null, $token->getLine()); - } else { - $length = $this->parseExpression(); - } - - $filter = $this->getFilter('slice', $token->getLine()); - $arguments = new Node([$arg, $length]); - $filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine()); - - $stream->expect(Token::PUNCTUATION_TYPE, ']'); - - return $filter; - } - - $stream->expect(Token::PUNCTUATION_TYPE, ']'); + if ('.' === $this->parser->getStream()->next()->getValue()) { + return $parsers->getValue($this->parser)->getByClass(DotExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken()); } - return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); + return $parsers->getValue($this->parser)->getByClass(SquareBracketExpressionParser::class)->parse($this->parser, $node, $this->parser->getCurrentToken()); } + /** + * @deprecated since Twig 3.21 + */ public function parseFilterExpression($node) { + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); + $this->parser->getStream()->next(); return $this->parseFilterExpressionRaw($node); } + /** + * @deprecated since Twig 3.21 + */ public function parseFilterExpressionRaw($node) { - if (\func_num_args() > 1) { - trigger_deprecation('twig/twig', '3.12', 'Passing a second argument to "%s()" is deprecated.', __METHOD__); - } + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); + $parsers = new \ReflectionProperty($this->parser, 'parsers'); + + $op = $parsers->getValue($this->parser)->getByClass(FilterExpressionParser::class); while (true) { - $token = $this->parser->getStream()->expect(Token::NAME_TYPE); - - if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) { - $arguments = new Node(); - } else { - $arguments = $this->parseArguments(true, false, true); - } - - $filter = $this->getFilter($token->getValue(), $token->getLine()); - - $ready = true; - if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) { - $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); - } - - if (!$ready = $this->readyNodes[$class]) { - trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); - } - - $node = new $class($node, $ready ? $filter : new ConstantExpression($filter->getName(), $token->getLine()), $arguments, $token->getLine()); - - if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { + $node = $op->parse($this->parser, $node, $this->parser->getCurrentToken()); + if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) { break; } - $this->parser->getStream()->next(); } @@ -607,21 +204,35 @@ class ExpressionParser /** * Parses arguments. * - * @param bool $namedArguments Whether to allow named arguments or not - * @param bool $definition Whether we are parsing arguments for a function (or macro) definition - * * @return Node * * @throws SyntaxError + * + * @deprecated since Twig 3.19 Use Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments() instead */ - public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false) + public function parseArguments() { + trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.', __METHOD__)); + + $parsePrimaryExpression = new \ReflectionMethod($this->parser, 'parsePrimaryExpression'); + + $namedArguments = false; + $definition = false; + if (\func_num_args() > 1) { + $definition = func_get_arg(1); + } + if (\func_num_args() > 0) { + trigger_deprecation('twig/twig', '3.15', 'Passing arguments to "%s()" is deprecated.', __METHOD__); + $namedArguments = func_get_arg(0); + } + $args = []; $stream = $this->parser->getStream(); - $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + $hasSpread = false; while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { - if (!empty($args)) { + if ($args) { $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); // if the comma above was a trailing comma, early exit the argument parse loop @@ -632,26 +243,33 @@ class ExpressionParser if ($definition) { $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); - $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); + $value = new ContextVariable($token->getValue(), $this->parser->getCurrentToken()->getLine()); } else { - $value = $this->parseExpression(0, $allowArrow); + if ($stream->nextIf(Token::SPREAD_TYPE)) { + $hasSpread = true; + $value = new SpreadUnary($this->parseExpression(), $stream->getCurrent()->getLine()); + } elseif ($hasSpread) { + throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } else { + $value = $this->parseExpression(); + } } $name = null; - if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) { - if (!$value instanceof NameExpression) { - throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); + if ($namedArguments && (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || (!$definition && $token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':')))) { + if (!$value instanceof ContextVariable) { + throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext()); } $name = $value->getAttribute('name'); if ($definition) { - $value = $this->parsePrimaryExpression(); + $value = $parsePrimaryExpression->invoke($this->parser); if (!$this->checkConstantExpression($value)) { throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext()); } } else { - $value = $this->parseExpression(0, $allowArrow); + $value = $this->parseExpression(); } } @@ -672,11 +290,16 @@ class ExpressionParser } $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); - return new Node($args); + return new Nodes($args); } + /** + * @deprecated since Twig 3.21, use "AbstractTokenParser::parseAssignmentExpression()" instead + */ public function parseAssignmentExpression() { + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated, use "AbstractTokenParser::parseAssignmentExpression()" instead.', __METHOD__); + $stream = $this->parser->getStream(); $targets = []; while (true) { @@ -687,22 +310,23 @@ class ExpressionParser } else { $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); } - $value = $token->getValue(); - if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) { - throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); - } - $targets[] = new AssignNameExpression($value, $token->getLine()); + $targets[] = new AssignContextVariable($token->getValue(), $token->getLine()); if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { break; } } - return new Node($targets); + return new Nodes($targets); } + /** + * @deprecated since Twig 3.21 + */ public function parseMultitargetExpression() { + trigger_deprecation('twig/twig', '3.21', 'The "%s()" method is deprecated.', __METHOD__); + $targets = []; while (true) { $targets[] = $this->parseExpression(); @@ -711,129 +335,11 @@ class ExpressionParser } } - return new Node($targets); - } - - private function parseNotTestExpression(Node $node): NotUnary - { - return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); - } - - private function parseTestExpression(Node $node): TestExpression - { - $stream = $this->parser->getStream(); - $test = $this->getTest($node->getTemplateLine()); - - $arguments = null; - if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { - $arguments = $this->parseArguments(true); - } elseif ($test->hasOneMandatoryArgument()) { - $arguments = new Node([0 => $this->parsePrimaryExpression()]); - } - - if ('defined' === $test->getName() && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { - $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); - $node->setAttribute('safe', true); - } - - $ready = $test instanceof TwigTest; - if (!isset($this->readyNodes[$class = $test->getNodeClass()])) { - $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); - } - - if (!$ready = $this->readyNodes[$class]) { - trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); - } - - return new $class($node, $ready ? $test : $test->getName(), $arguments, $this->parser->getCurrentToken()->getLine()); - } - - private function getTest(int $line): TwigTest - { - $stream = $this->parser->getStream(); - $name = $stream->expect(Token::NAME_TYPE)->getValue(); - - if ($stream->test(Token::NAME_TYPE)) { - // try 2-words tests - $name = $name.' '.$this->parser->getCurrentToken()->getValue(); - - if ($test = $this->env->getTest($name)) { - $stream->next(); - } - } else { - $test = $this->env->getTest($name); - } - - if (!$test) { - $e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); - $e->addSuggestions($name, array_keys($this->env->getTests())); - - throw $e; - } - - if ($test->isDeprecated()) { - $stream = $this->parser->getStream(); - $message = \sprintf('Twig Test "%s" is deprecated', $test->getName()); - - if ($test->getAlternative()) { - $message .= \sprintf('. Use "%s" instead', $test->getAlternative()); - } - $src = $stream->getSourceContext(); - $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); - - trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message); - } - - return $test; - } - - private function getFunction(string $name, int $line): TwigFunction - { - if (!$function = $this->env->getFunction($name)) { - $e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); - $e->addSuggestions($name, array_keys($this->env->getFunctions())); - - throw $e; - } - - if ($function->isDeprecated()) { - $message = \sprintf('Twig Function "%s" is deprecated', $function->getName()); - if ($function->getAlternative()) { - $message .= \sprintf('. Use "%s" instead', $function->getAlternative()); - } - $src = $this->parser->getStream()->getSourceContext(); - $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - - trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message); - } - - return $function; - } - - private function getFilter(string $name, int $line): TwigFilter - { - if (!$filter = $this->env->getFilter($name)) { - $e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); - $e->addSuggestions($name, array_keys($this->env->getFilters())); - - throw $e; - } - - if ($filter->isDeprecated()) { - $message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName()); - if ($filter->getAlternative()) { - $message .= \sprintf('. Use "%s" instead', $filter->getAlternative()); - } - $src = $this->parser->getStream()->getSourceContext(); - $message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - - trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message); - } - - return $filter; + return new Nodes($targets); } // checks that the node only contains "constant" elements + // to be removed in 4.0 private function checkConstantExpression(Node $node): bool { if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression @@ -850,4 +356,14 @@ class ExpressionParser return true; } + + /** + * @deprecated since Twig 3.19 Use Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments() instead + */ + public function parseOnlyArguments() + { + trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.', __METHOD__)); + + return $this->parseArguments(); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/AbstractExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/AbstractExpressionParser.php new file mode 100644 index 000000000..b8b963a1f --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/AbstractExpressionParser.php @@ -0,0 +1,35 @@ +value, $this->getName()); + } + + public function getPrecedenceChange(): ?PrecedenceChange + { + return null; + } + + public function getAliases(): array + { + return []; + } + + public function getOperatorTokens(): array + { + return [$this->getName(), ...$this->getAliases()]; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/ExpressionParserDescriptionInterface.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/ExpressionParserDescriptionInterface.php new file mode 100644 index 000000000..686f8a59f --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/ExpressionParserDescriptionInterface.php @@ -0,0 +1,17 @@ + getOperatorTokens() Returns the operator token strings that this expression parser handles. + * These are the strings that should be recognized as operator tokens by the Lexer, + * and used to look up the parser in the registry. + * For most parsers, this returns the name and aliases. Parsers that don't handle + * operator tokens (like LiteralExpressionParser) should return an empty array. + * This method will be added to the interface in Twig 4.0. + */ +interface ExpressionParserInterface +{ + public function __toString(): string; + + public function getName(): string; + + public function getPrecedence(): int; + + public function getPrecedenceChange(): ?PrecedenceChange; + + /** + * @return array + */ + public function getAliases(): array; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/ExpressionParserType.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/ExpressionParserType.php new file mode 100644 index 000000000..8c21a8d76 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/ExpressionParserType.php @@ -0,0 +1,33 @@ + + * + * @internal + */ +final class ExpressionParsers implements \IteratorAggregate +{ + /** + * @var array, array> + */ + private array $parsersByName = []; + + /** + * @var array, ExpressionParserInterface> + */ + private array $parsersByClass = []; + + /** + * @var \WeakMap>|null + */ + private ?\WeakMap $precedenceChanges = null; + + /** + * @param array $parsers + */ + public function __construct(array $parsers = []) + { + $this->add($parsers); + } + + /** + * @param array $parsers + * + * @return $this + */ + public function add(array $parsers): static + { + foreach ($parsers as $parser) { + if ($parser->getPrecedence() > 512 || $parser->getPrecedence() < 0) { + trigger_deprecation('twig/twig', '3.21', 'Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence()); + // throw new \InvalidArgumentException(\sprintf('Precedence for "%s" must be between 0 and 512, got %d.', $parser->getName(), $parser->getPrecedence())); + } + $interface = $parser instanceof PrefixExpressionParserInterface ? PrefixExpressionParserInterface::class : InfixExpressionParserInterface::class; + $this->parsersByClass[$parser::class] = $parser; + foreach (self::getOperatorTokensFor($parser) as $token) { + $this->parsersByName[$interface][$token] = $parser; + } + } + + return $this; + } + + /** + * @template T of ExpressionParserInterface + * + * @param class-string $class + * + * @return T|null + */ + public function getByClass(string $class): ?ExpressionParserInterface + { + return $this->parsersByClass[$class] ?? null; + } + + /** + * @template T of ExpressionParserInterface + * + * @param class-string $interface + * + * @return T|null + */ + public function getByName(string $interface, string $name): ?ExpressionParserInterface + { + return $this->parsersByName[$interface][$name] ?? null; + } + + public function getIterator(): \Traversable + { + $seen = []; + foreach ($this->parsersByName as $parsers) { + foreach ($parsers as $parser) { + $id = spl_object_id($parser); + if (!isset($seen[$id])) { + $seen[$id] = true; + yield $parser; + } + } + } + foreach ($this->parsersByClass as $parser) { + $id = spl_object_id($parser); + if (!isset($seen[$id])) { + $seen[$id] = true; + yield $parser; + } + } + } + + /** + * @internal + * + * @return \WeakMap> + */ + public function getPrecedenceChanges(): \WeakMap + { + if (null === $this->precedenceChanges) { + $this->precedenceChanges = new \WeakMap(); + foreach ($this as $ep) { + if (!$ep->getPrecedenceChange()) { + continue; + } + $min = min($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence()); + $max = max($ep->getPrecedenceChange()->getNewPrecedence(), $ep->getPrecedence()); + foreach ($this as $e) { + if ($e->getPrecedence() > $min && $e->getPrecedence() < $max) { + if (!isset($this->precedenceChanges[$e])) { + $this->precedenceChanges[$e] = []; + } + $this->precedenceChanges[$e][] = $ep; + } + } + } + } + + return $this->precedenceChanges; + } + + /** + * @internal + * + * @return array + */ + public static function getOperatorTokensFor(ExpressionParserInterface $parser): array + { + if (method_exists($parser, 'getOperatorTokens')) { + return $parser->getOperatorTokens(); + } + + trigger_deprecation('twig/twig', '3.24', 'Not implementing the "getOperatorTokens()" method in "%s" is deprecated. This method will be part of the "%s" interface in 4.0.', $parser::class, ExpressionParserInterface::class); + + return [$parser->getName(), ...$parser->getAliases()]; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ArgumentsTrait.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ArgumentsTrait.php new file mode 100644 index 000000000..fe2013843 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ArgumentsTrait.php @@ -0,0 +1,84 @@ +parseNamedArguments($parser, $parseOpenParenthesis) as $k => $n) { + $arguments->addElement($n, \is_int($k) || !$preserveNames ? new LocalVariable($k, $line) : new ConstantExpression($k, $line)); + } + + return $arguments; + } + + private function parseNamedArguments(Parser $parser, bool $parseOpenParenthesis = true): Nodes + { + $args = []; + $stream = $parser->getStream(); + if ($parseOpenParenthesis) { + $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + } + $hasSpread = false; + while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { + if ($args) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + + // if the comma above was a trailing comma, early exit the argument parse loop + if ($stream->test(Token::PUNCTUATION_TYPE, ')')) { + break; + } + } + + $value = $parser->parseExpression(); + if ($value instanceof SpreadUnary) { + $hasSpread = true; + } elseif ($hasSpread) { + throw new SyntaxError('Normal arguments must be placed before argument unpacking.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $name = null; + if ($value instanceof SetBinary) { + $name = $value->getNode('left')->getAttribute('name'); + $value = $value->getNode('right'); + } elseif (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) { + if (!$value instanceof ContextVariable) { + throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext()); + } + $name = $value->getAttribute('name'); + $value = $parser->parseExpression(); + } + + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } + } + $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Nodes($args); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ArrowExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ArrowExpressionParser.php new file mode 100644 index 000000000..c8630da41 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ArrowExpressionParser.php @@ -0,0 +1,53 @@ +parseExpression(), $expr, $token->getLine()); + } + + public function getName(): string + { + return '=>'; + } + + public function getDescription(): string + { + return 'Arrow function (x => expr)'; + } + + public function getPrecedence(): int + { + return 250; + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/AssignmentExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/AssignmentExpressionParser.php new file mode 100644 index 000000000..66d5c77be --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/AssignmentExpressionParser.php @@ -0,0 +1,80 @@ +getLine(), $parser->getStream()->getSourceContext()); + } + $right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence()); + $right = match ($this->getName()) { + '=' => $right, + default => throw new \LogicException(\sprintf('Unknown operator: %s.', $this->getName())), + }; + + if ($left instanceof ArrayExpression) { + $pairs = $left->getKeyValuePairs(); + $isSequence = $left->isSequence(); + if (!$pairs || ($isSequence && !array_filter($pairs, static fn (array $pair): bool => !$pair['value'] instanceof EmptyExpression))) { + throw new SyntaxError('Cannot destructure to an empty list of variables.', $token->getLine(), $parser->getStream()->getSourceContext()); + } + + foreach ($pairs as $i => $pair) { + if ($pair['value'] instanceof ContextVariable && !$pair['value'] instanceof AssignContextVariable) { + $left->setNode(2 * $i + 1, new AssignContextVariable($pair['value']->getAttribute('name'), $pair['value']->getTemplateLine())); + } + } + + if ($isSequence) { + return new SequenceDestructuringSetBinary($left, $right, $token->getLine()); + } + + return new ObjectDestructuringSetBinary($left, $right, $token->getLine()); + } + + return new SetBinary($left, $right, $token->getLine()); + } + + public function getDescription(): string + { + return 'Assignment operator'; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/BinaryOperatorExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/BinaryOperatorExpressionParser.php new file mode 100644 index 000000000..4c66da73b --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/BinaryOperatorExpressionParser.php @@ -0,0 +1,80 @@ + */ + private string $nodeClass, + private string $name, + private int $precedence, + private InfixAssociativity $associativity = InfixAssociativity::Left, + private ?PrecedenceChange $precedenceChange = null, + private ?string $description = null, + private array $aliases = [], + ) { + } + + /** + * @return AbstractBinary + */ + public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression + { + $right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence()); + + return new ($this->nodeClass)($left, $right, $token->getLine()); + } + + public function getAssociativity(): InfixAssociativity + { + return $this->associativity; + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): string + { + return $this->description ?? ''; + } + + public function getPrecedence(): int + { + return $this->precedence; + } + + public function getPrecedenceChange(): ?PrecedenceChange + { + return $this->precedenceChange; + } + + public function getAliases(): array + { + return $this->aliases; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ConditionalTernaryExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ConditionalTernaryExpressionParser.php new file mode 100644 index 000000000..9707c0a04 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/ConditionalTernaryExpressionParser.php @@ -0,0 +1,62 @@ +parseExpression($this->getPrecedence()); + if ($parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + // Ternary operator (expr ? expr2 : expr3) + $else = $parser->parseExpression($this->getPrecedence()); + } else { + // Ternary without else (expr ? expr2) + $else = new ConstantExpression('', $token->getLine()); + } + + return new ConditionalTernary($left, $then, $else, $token->getLine()); + } + + public function getName(): string + { + return '?'; + } + + public function getDescription(): string + { + return 'Conditional operator (a ? b : c)'; + } + + public function getPrecedence(): int + { + return 0; + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/DotExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/DotExpressionParser.php new file mode 100644 index 000000000..fbda51667 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/DotExpressionParser.php @@ -0,0 +1,109 @@ +getValue(); + $stream = $parser->getStream(); + $token = $stream->getCurrent(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + $type = Template::ANY_CALL; + + if ($stream->nextIf(Token::OPERATOR_TYPE, '(')) { + $attribute = $parser->parseExpression(); + $stream->expect(Token::PUNCTUATION_TYPE, ')'); + } else { + $token = $stream->next(); + if ( + $token->test(Token::NAME_TYPE) + || $token->test(Token::NUMBER_TYPE) + || ($token->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) + ) { + $attribute = new ConstantExpression($token->getValue(), $token->getLine()); + } else { + throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type "%s".', $token->getValue(), $token->toEnglish()), $token->getLine(), $stream->getSourceContext()); + } + } + + $isMacroTarget = $expr instanceof NameExpression + && ( + null !== $parser->getImportedSymbol('template', $expr->getAttribute('name')) + || '_self' === $expr->getAttribute('name') + ); + + if ($stream->test(Token::OPERATOR_TYPE, '(')) { + $type = Template::METHOD_CALL; + $arguments = $this->parseCallableArguments($parser, $token->getLine(), preserveNames: $isMacroTarget); + } + + if ($isMacroTarget) { + $name = $attribute instanceof ConstantExpression ? (string) $attribute->getAttribute('value') : $attribute; + $node = new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), $name, $arguments, $expr->getTemplateLine()); + $node->setHasCallParentheses(Template::METHOD_CALL === $type); + + return $node; + } + + return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno, $nullSafe); + } + + public function getName(): string + { + return '.'; + } + + public function getAliases(): array + { + return ['?.']; + } + + public function getDescription(): string + { + return 'Get an attribute on a variable'; + } + + public function getPrecedence(): int + { + return 512; + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/FilterExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/FilterExpressionParser.php new file mode 100644 index 000000000..0bbe6b409 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/FilterExpressionParser.php @@ -0,0 +1,85 @@ +getStream(); + $token = $stream->expect(Token::NAME_TYPE); + $line = $token->getLine(); + + if (!$stream->test(Token::OPERATOR_TYPE, '(')) { + $arguments = new EmptyNode(); + } else { + $arguments = $this->parseNamedArguments($parser); + } + + $filter = $parser->getFilter($token->getValue(), $line); + + $ready = true; + if (!isset($this->readyNodes[$class = $filter->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFilter" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($expr, $ready ? $filter : new ConstantExpression($filter->getName(), $line), $arguments, $line); + } + + public function getName(): string + { + return '|'; + } + + public function getDescription(): string + { + return 'Twig filter call'; + } + + public function getPrecedence(): int + { + return 512; + } + + public function getPrecedenceChange(): ?PrecedenceChange + { + return new PrecedenceChange('twig/twig', '3.21', 300); + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/FunctionExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/FunctionExpressionParser.php new file mode 100644 index 000000000..2b8e93332 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/FunctionExpressionParser.php @@ -0,0 +1,105 @@ +getLine(); + if (!$expr instanceof NameExpression) { + throw new SyntaxError('Function name must be an identifier.', $line, $parser->getStream()->getSourceContext()); + } + + $name = $expr->getAttribute('name'); + + // A bare call to a macro imported via "from" is syntactically a function call; + // it is resolved through the "function" imported symbol registered by + // FromTokenParser, which maps the local alias to the macro name and the + // template it comes from. + if (null !== $alias = $parser->getImportedSymbol('function', $name)) { + $arguments = $this->parseCallableArguments($parser, $line, false, true); + $node = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], $arguments, $line); + $node->setHasCallParentheses(true); + + return $node; + } + + $args = $this->parseNamedArguments($parser, false); + + $function = $parser->getFunction($name, $line); + + if ($function->getParserCallable()) { + $fakeNode = new EmptyNode($line); + $fakeNode->setSourceContext($parser->getStream()->getSourceContext()); + + $node = ($function->getParserCallable())($parser, $fakeNode, $args, $line); + // remember the original function name so the sandbox can enforce + // the `allowedFunctions` allow-list even though the parser callable + // returned a specialized node (e.g. `parent`, `block`, `attribute`). + $node->setAttribute('sandboxed_function_name', $name); + $node->setAttribute('sandboxed_function', $function); + + return $node; + } + + if (!isset($this->readyNodes[$class = $function->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigFunction" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($ready ? $function : $function->getName(), $args, $line); + } + + public function getName(): string + { + return '('; + } + + public function getDescription(): string + { + return 'Twig function call'; + } + + public function getPrecedence(): int + { + return 512; + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/IsExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/IsExpressionParser.php new file mode 100644 index 000000000..beaa1743d --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/IsExpressionParser.php @@ -0,0 +1,91 @@ +getStream(); + $test = $parser->getTest($token->getLine()); + + $arguments = null; + if ($stream->test(Token::OPERATOR_TYPE, '(')) { + $arguments = $this->parseNamedArguments($parser); + } elseif ($test->hasOneMandatoryArgument()) { + $arguments = new Nodes([0 => $parser->parseExpression($this->getPrecedence())]); + } + + if ('defined' === $test->getName()) { + if ($expr instanceof NameExpression && null !== $alias = $parser->getImportedSymbol('function', $expr->getAttribute('name'))) { + $expr = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], new ArrayExpression([], $expr->getTemplateLine()), $expr->getTemplateLine()); + $expr->setHasCallParentheses(false); + } + + if ($expr instanceof MacroReferenceExpression && $expr->hasCallParentheses()) { + trigger_deprecation('twig/twig', '3.29', 'Using parentheses when testing a macro with the "defined" test is deprecated and will throw a SyntaxError in Twig 4.0; remove the parentheses after the macro name in "%s" at line %d.', $stream->getSourceContext()->getName(), $expr->getTemplateLine()); + } + } + + $ready = $test instanceof TwigTest; + if (!isset($this->readyNodes[$class = $test->getNodeClass()])) { + $this->readyNodes[$class] = (bool) (new \ReflectionClass($class))->getConstructor()->getAttributes(FirstClassTwigCallableReady::class); + } + + if (!$ready = $this->readyNodes[$class]) { + trigger_deprecation('twig/twig', '3.12', 'Twig node "%s" is not marked as ready for passing a "TwigTest" in the constructor instead of its name; please update your code and then add #[FirstClassTwigCallableReady] attribute to the constructor.', $class); + } + + return new $class($expr, $ready ? $test : $test->getName(), $arguments, $stream->getCurrent()->getLine()); + } + + public function getPrecedence(): int + { + return 100; + } + + public function getName(): string + { + return 'is'; + } + + public function getDescription(): string + { + return 'Twig tests'; + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/IsNotExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/IsNotExpressionParser.php new file mode 100644 index 000000000..1e1085aa8 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/IsNotExpressionParser.php @@ -0,0 +1,33 @@ +getLine()); + } + + public function getName(): string + { + return 'is not'; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/SquareBracketExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/SquareBracketExpressionParser.php new file mode 100644 index 000000000..c47c91dee --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Infix/SquareBracketExpressionParser.php @@ -0,0 +1,91 @@ +getStream(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + + // slice? + $slice = false; + if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + $attribute = new ConstantExpression(0, $token->getLine()); + } else { + $attribute = $parser->parseExpression(); + } + + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + $length = new ConstantExpression(null, $token->getLine()); + } else { + $length = $parser->parseExpression(); + } + + $filter = $parser->getFilter('slice', $token->getLine()); + $arguments = new Nodes([$attribute, $length]); + $filter = new ($filter->getNodeClass())($expr, $filter, $arguments, $token->getLine()); + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + + return $filter; + } + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + + return new GetAttrExpression($expr, $attribute, $arguments, Template::ARRAY_CALL, $lineno); + } + + public function getName(): string + { + return '['; + } + + public function getDescription(): string + { + return 'Array access'; + } + + public function getPrecedence(): int + { + return 512; + } + + public function getAssociativity(): InfixAssociativity + { + return InfixAssociativity::Left; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/InfixAssociativity.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/InfixAssociativity.php new file mode 100644 index 000000000..3aeccce45 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/InfixAssociativity.php @@ -0,0 +1,18 @@ + + */ +class PrecedenceChange +{ + public function __construct( + private string $package, + private string $version, + private int $newPrecedence, + ) { + } + + public function getPackage(): string + { + return $this->package; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getNewPrecedence(): int + { + return $this->newPrecedence; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/GroupingExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/GroupingExpressionParser.php new file mode 100644 index 000000000..95a11762f --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/GroupingExpressionParser.php @@ -0,0 +1,88 @@ +getStream(); + $expr = $parser->parseExpression($this->getPrecedence()); + + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ')')) { + if (!$stream->test(Token::OPERATOR_TYPE, '=>')) { + return $expr->setExplicitParentheses(); + } + + return new ListExpression([self::toAssignContextVariable($expr)], $token->getLine()); + } + + // determine if we are parsing an arrow function arguments + if (!$stream->test(Token::PUNCTUATION_TYPE, ',')) { + $stream->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + } + + $names = [$expr]; + while (true) { + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ')')) { + break; + } + $stream->expect(Token::PUNCTUATION_TYPE, ','); + $token = $stream->expect(Token::NAME_TYPE); + $names[] = new ContextVariable($token->getValue(), $token->getLine()); + } + + if (!$stream->test(Token::OPERATOR_TYPE, '=>')) { + throw new SyntaxError('A list of variables must be followed by an arrow.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + return new ListExpression(array_map(self::toAssignContextVariable(...), $names), $token->getLine()); + } + + private static function toAssignContextVariable(AbstractExpression $expr): AssignContextVariable + { + if (!$expr instanceof ContextVariable) { + throw new SyntaxError('A list must only contain variables.', $expr->getTemplateLine(), $expr->getSourceContext()); + } + + return $expr instanceof AssignContextVariable ? $expr : new AssignContextVariable($expr->getAttribute('name'), $expr->getTemplateLine()); + } + + public function getName(): string + { + return '('; + } + + public function getDescription(): string + { + return 'Explicit group expression (a)'; + } + + public function getPrecedence(): int + { + return 0; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/LiteralExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/LiteralExpressionParser.php new file mode 100644 index 000000000..b467d1aa5 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/LiteralExpressionParser.php @@ -0,0 +1,233 @@ +getStream(); + switch (true) { + case $token->test(Token::NAME_TYPE): + $stream->next(); + switch ($token->getValue()) { + case 'true': + case 'TRUE': + return new ConstantExpression(true, $token->getLine()); + + case 'false': + case 'FALSE': + return new ConstantExpression(false, $token->getLine()); + + case 'none': + case 'NONE': + case 'null': + case 'NULL': + return new ConstantExpression(null, $token->getLine()); + + default: + return new ContextVariable($token->getValue(), $token->getLine()); + } + + // no break + case $token->test(Token::NUMBER_TYPE): + $stream->next(); + + return new ConstantExpression($token->getValue(), $token->getLine()); + + case $token->test(Token::STRING_TYPE): + case $token->test(Token::INTERPOLATION_START_TYPE): + return $this->parseStringExpression($parser); + + case $token->test(Token::PUNCTUATION_TYPE): + // In 4.0, we should always return the node or throw an error for default + if ($node = match ($token->getValue()) { + '{' => $this->parseMappingExpression($parser), + default => null, + }) { + return $node; + } + + // no break + case $token->test(Token::OPERATOR_TYPE): + if ('[' === $token->getValue()) { + return $this->parseSequenceExpression($parser); + } + + if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $stream->next(); + + return new ContextVariable($token->getValue(), $token->getLine()); + } + + // no break + default: + throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', $token->toEnglish(), $token->getValue()), $token->getLine(), $stream->getSourceContext()); + } + } + + public function getName(): string + { + return 'literal'; + } + + public function getOperatorTokens(): array + { + return []; + } + + public function getDescription(): string + { + return 'A literal value (boolean, string, number, sequence, mapping, ...)'; + } + + public function getPrecedence(): int + { + // not used + return 0; + } + + private function parseStringExpression(Parser $parser) + { + $stream = $parser->getStream(); + + $nodes = []; + // a string cannot be followed by another string in a single expression + $nextCanBeString = true; + while (true) { + if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { + $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); + $nextCanBeString = false; + } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { + $nodes[] = $parser->parseExpression(); + $stream->expect(Token::INTERPOLATION_END_TYPE); + $nextCanBeString = true; + } else { + break; + } + } + + $expr = array_shift($nodes); + foreach ($nodes as $node) { + $expr = new ConcatBinary($expr, $node, $node->getTemplateLine()); + } + + return $expr; + } + + private function parseSequenceExpression(Parser $parser) + { + $stream = $parser->getStream(); + $stream->expect(Token::OPERATOR_TYPE, '[', 'A sequence element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A sequence element must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + // Check for empty slots (comma with no expression) + if ($stream->test(Token::PUNCTUATION_TYPE, ',')) { + $node->addElement(new EmptyExpression($stream->getCurrent()->getLine())); + } else { + $node->addElement($parser->parseExpression()); + } + } + $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed'); + + return $node; + } + + private function parseMappingExpression(Parser $parser) + { + $stream = $parser->getStream(); + $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A mapping value must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + if ($stream->test(Token::OPERATOR_TYPE, '...')) { + $node->addElement($parser->parseExpression()); + + continue; + } + + // a mapping key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if ($token = $stream->nextIf(Token::NAME_TYPE)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + + // {a} is a shortcut for {a:a} + if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) { + $value = new ContextVariable($key->getAttribute('value'), $key->getTemplateLine()); + $node->addElement($value, $key); + continue; + } + } elseif (($token = $stream->nextIf(Token::STRING_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + } elseif ($stream->test(Token::OPERATOR_TYPE, '(')) { + $key = $parser->parseExpression(); + } else { + $current = $stream->getCurrent(); + + throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', $current->toEnglish(), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A mapping key must be followed by a colon (:)'); + $value = $parser->parseExpression(); + + $node->addElement($value, $key); + } + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + + return $node; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/UnaryOperatorExpressionParser.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/UnaryOperatorExpressionParser.php new file mode 100644 index 000000000..801390097 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/Prefix/UnaryOperatorExpressionParser.php @@ -0,0 +1,72 @@ + */ + private string $nodeClass, + private string $name, + private int $precedence, + private ?PrecedenceChange $precedenceChange = null, + private ?string $description = null, + private array $aliases = [], + private ?int $operandPrecedence = null, + ) { + } + + /** + * @return AbstractUnary + */ + public function parse(Parser $parser, Token $token): AbstractExpression + { + return new ($this->nodeClass)($parser->parseExpression($this->operandPrecedence ?? $this->precedence), $token->getLine()); + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): string + { + return $this->description ?? ''; + } + + public function getPrecedence(): int + { + return $this->precedence; + } + + public function getPrecedenceChange(): ?PrecedenceChange + { + return $this->precedenceChange; + } + + public function getAliases(): array + { + return $this->aliases; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/PrefixExpressionParserInterface.php b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/PrefixExpressionParserInterface.php new file mode 100644 index 000000000..8e39dad37 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/ExpressionParser/PrefixExpressionParserInterface.php @@ -0,0 +1,25 @@ +getFileName(); + if (!is_file($filename)) { + return 0; + } + + $lastModified = filemtime($filename); + + // Track modifications of the runtime class if it exists and follows the naming convention + if (str_ends_with($filename, 'Extension.php') && is_file($filename = substr($filename, 0, -13).'Runtime.php')) { + $lastModified = max($lastModified, filemtime($filename)); + } + + return $lastModified; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/AttributeExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/AttributeExtension.php new file mode 100644 index 000000000..623fd4f8e --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/AttributeExtension.php @@ -0,0 +1,179 @@ + + */ +final class AttributeExtension extends AbstractExtension +{ + private array $filters; + private array $functions; + private array $tests; + + /** + * Use a runtime class using PHP attributes to define filters, functions, and tests. + * + * @param class-string $class + */ + public function __construct(private string $class) + { + } + + /** + * @return class-string + */ + public function getClass(): string + { + return $this->class; + } + + public function getFilters(): array + { + if (!isset($this->filters)) { + $this->initFromAttributes(); + } + + return $this->filters; + } + + public function getFunctions(): array + { + if (!isset($this->functions)) { + $this->initFromAttributes(); + } + + return $this->functions; + } + + public function getTests(): array + { + if (!isset($this->tests)) { + $this->initFromAttributes(); + } + + return $this->tests; + } + + public function getLastModified(): int + { + return max( + filemtime(__FILE__), + is_file($filename = (new \ReflectionClass($this->getClass()))->getFileName()) ? filemtime($filename) : 0, + ); + } + + private function initFromAttributes(): void + { + $filters = $functions = $tests = []; + $reflectionClass = new \ReflectionClass($this->getClass()); + foreach ($reflectionClass->getMethods() as $method) { + foreach ($method->getAttributes(AsTwigFilter::class) as $reflectionAttribute) { + /** @var AsTwigFilter $attribute */ + $attribute = $reflectionAttribute->newInstance(); + + $callable = new TwigFilter($attribute->name, [$reflectionClass->name, $method->getName()], [ + 'needs_context' => $attribute->needsContext ?? false, + 'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method), + 'needs_charset' => $attribute->needsCharset ?? false, + 'needs_is_sandboxed' => $attribute->needsIsSandboxed ?? false, + 'is_variadic' => $method->isVariadic(), + 'is_safe' => $attribute->isSafe, + 'is_safe_callback' => $attribute->isSafeCallback, + 'pre_escape' => $attribute->preEscape, + 'preserves_safety' => $attribute->preservesSafety, + 'always_allowed_in_sandbox' => $attribute->alwaysAllowedInSandbox ?? false, + 'deprecation_info' => $attribute->deprecationInfo, + ]); + + if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) { + throw new \LogicException(\sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigFilter, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters())); + } + + $filters[$attribute->name] = $callable; + } + + foreach ($method->getAttributes(AsTwigFunction::class) as $reflectionAttribute) { + /** @var AsTwigFunction $attribute */ + $attribute = $reflectionAttribute->newInstance(); + + $callable = new TwigFunction($attribute->name, [$reflectionClass->name, $method->getName()], [ + 'needs_context' => $attribute->needsContext ?? false, + 'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method), + 'needs_charset' => $attribute->needsCharset ?? false, + 'needs_is_sandboxed' => $attribute->needsIsSandboxed ?? false, + 'is_variadic' => $method->isVariadic(), + 'is_safe' => $attribute->isSafe, + 'is_safe_callback' => $attribute->isSafeCallback, + 'always_allowed_in_sandbox' => $attribute->alwaysAllowedInSandbox ?? false, + 'deprecation_info' => $attribute->deprecationInfo, + ]); + + if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) { + throw new \LogicException(\sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigFunction, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters())); + } + + $functions[$attribute->name] = $callable; + } + + foreach ($method->getAttributes(AsTwigTest::class) as $reflectionAttribute) { + /** @var AsTwigTest $attribute */ + $attribute = $reflectionAttribute->newInstance(); + + $callable = new TwigTest($attribute->name, [$reflectionClass->name, $method->getName()], [ + 'needs_context' => $attribute->needsContext ?? false, + 'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method), + 'needs_charset' => $attribute->needsCharset ?? false, + 'needs_is_sandboxed' => $attribute->needsIsSandboxed ?? false, + 'is_variadic' => $method->isVariadic(), + 'always_allowed_in_sandbox' => $attribute->alwaysAllowedInSandbox ?? false, + 'deprecation_info' => $attribute->deprecationInfo, + ]); + + if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) { + throw new \LogicException(\sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigTest, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters())); + } + + $tests[$attribute->name] = $callable; + } + } + + // Assign all at the end to avoid inconsistent state in case of exception + $this->filters = array_values($filters); + $this->functions = array_values($functions); + $this->tests = array_values($tests); + } + + /** + * Detect if the first argument of the method is the environment. + */ + private function needsEnvironment(\ReflectionFunctionAbstract $function): bool + { + if (!$parameters = $function->getParameters()) { + return false; + } + + return $parameters[0]->getType() instanceof \ReflectionNamedType + && Environment::class === $parameters[0]->getType()->getName() + && !$parameters[0]->isVariadic(); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php index 3ed27a35c..f5f5e3952 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/CoreExtension.php @@ -11,11 +11,27 @@ namespace Twig\Extension; +use Twig\DeprecatedCallableInfo; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; -use Twig\ExpressionParser; +use Twig\ExpressionParser\Infix\ArrowExpressionParser; +use Twig\ExpressionParser\Infix\AssignmentExpressionParser; +use Twig\ExpressionParser\Infix\BinaryOperatorExpressionParser; +use Twig\ExpressionParser\Infix\ConditionalTernaryExpressionParser; +use Twig\ExpressionParser\Infix\DotExpressionParser; +use Twig\ExpressionParser\Infix\FilterExpressionParser; +use Twig\ExpressionParser\Infix\FunctionExpressionParser; +use Twig\ExpressionParser\Infix\IsExpressionParser; +use Twig\ExpressionParser\Infix\IsNotExpressionParser; +use Twig\ExpressionParser\Infix\SquareBracketExpressionParser; +use Twig\ExpressionParser\InfixAssociativity; +use Twig\ExpressionParser\PrecedenceChange; +use Twig\ExpressionParser\Prefix\GroupingExpressionParser; +use Twig\ExpressionParser\Prefix\LiteralExpressionParser; +use Twig\ExpressionParser\Prefix\UnaryOperatorExpressionParser; +use Twig\MacroNamespace; use Twig\Markup; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\Binary\AddBinary; @@ -25,6 +41,7 @@ use Twig\Node\Expression\Binary\BitwiseOrBinary; use Twig\Node\Expression\Binary\BitwiseXorBinary; use Twig\Node\Expression\Binary\ConcatBinary; use Twig\Node\Expression\Binary\DivBinary; +use Twig\Node\Expression\Binary\ElvisBinary; use Twig\Node\Expression\Binary\EndsWithBinary; use Twig\Node\Expression\Binary\EqualBinary; use Twig\Node\Expression\Binary\FloorDivBinary; @@ -40,17 +57,21 @@ use Twig\Node\Expression\Binary\ModBinary; use Twig\Node\Expression\Binary\MulBinary; use Twig\Node\Expression\Binary\NotEqualBinary; use Twig\Node\Expression\Binary\NotInBinary; +use Twig\Node\Expression\Binary\NotSameAsBinary; +use Twig\Node\Expression\Binary\NullCoalesceBinary; use Twig\Node\Expression\Binary\OrBinary; use Twig\Node\Expression\Binary\PowerBinary; use Twig\Node\Expression\Binary\RangeBinary; +use Twig\Node\Expression\Binary\SameAsBinary; use Twig\Node\Expression\Binary\SpaceshipBinary; use Twig\Node\Expression\Binary\StartsWithBinary; use Twig\Node\Expression\Binary\SubBinary; +use Twig\Node\Expression\Binary\XorBinary; use Twig\Node\Expression\BlockReferenceExpression; use Twig\Node\Expression\Filter\DefaultFilter; use Twig\Node\Expression\FunctionNode\EnumCasesFunction; +use Twig\Node\Expression\FunctionNode\EnumFunction; use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\NullCoalesceExpression; use Twig\Node\Expression\ParentExpression; use Twig\Node\Expression\Test\ConstantTest; use Twig\Node\Expression\Test\DefinedTest; @@ -59,12 +80,16 @@ use Twig\Node\Expression\Test\EvenTest; use Twig\Node\Expression\Test\NullTest; use Twig\Node\Expression\Test\OddTest; use Twig\Node\Expression\Test\SameasTest; +use Twig\Node\Expression\Test\TrueTest; use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; +use Twig\Node\Expression\Unary\SpreadUnary; use Twig\Node\Node; -use Twig\NodeVisitor\MacroAutoImportNodeVisitor; +use Twig\NodeVisitor\CorrectnessNodeVisitor; use Twig\Parser; +use Twig\Sandbox\SecurityNotAllowedMethodError; +use Twig\Sandbox\SecurityNotAllowedPropertyError; use Twig\Source; use Twig\Template; use Twig\TemplateWrapper; @@ -77,6 +102,7 @@ use Twig\TokenParser\ExtendsTokenParser; use Twig\TokenParser\FlushTokenParser; use Twig\TokenParser\ForTokenParser; use Twig\TokenParser\FromTokenParser; +use Twig\TokenParser\GuardTokenParser; use Twig\TokenParser\IfTokenParser; use Twig\TokenParser\ImportTokenParser; use Twig\TokenParser\IncludeTokenParser; @@ -92,9 +118,25 @@ use Twig\Util\CallableArgumentsExtractor; final class CoreExtension extends AbstractExtension { + public const ARRAY_LIKE_CLASSES = [ + 'ArrayIterator', + 'ArrayObject', + 'CachingIterator', + 'RecursiveArrayIterator', + 'RecursiveCachingIterator', + 'SplDoublyLinkedList', + 'SplFixedArray', + 'SplObjectStorage', + 'SplQueue', + 'SplStack', + 'WeakMap', + ]; + + private const DEFAULT_TRIM_CHARS = " \t\n\r\0\x0B"; + private $dateFormats = ['F j, Y H:i', '%d days']; private $numberFormat = [0, '.', ',']; - private $timezone = null; + private $timezone; /** * Sets the default format to be used by the date filter. @@ -102,7 +144,7 @@ final class CoreExtension extends AbstractExtension * @param string|null $format The default date format string * @param string|null $dateIntervalFormat The default date interval format string */ - public function setDateFormat($format = null, $dateIntervalFormat = null) + public function setDateFormat($format = null, $dateIntervalFormat = null): void { if (null !== $format) { $this->dateFormats[0] = $format; @@ -128,7 +170,7 @@ final class CoreExtension extends AbstractExtension * * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object */ - public function setTimezone($timezone) + public function setTimezone($timezone): void { $this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone); } @@ -154,7 +196,7 @@ final class CoreExtension extends AbstractExtension * @param string $decimalPoint the character(s) to use for the decimal point * @param string $thousandSep the character(s) to use for the thousands separator */ - public function setNumberFormat($decimal, $decimalPoint, $thousandSep) + public function setNumberFormat($decimal, $decimalPoint, $thousandSep): void { $this->numberFormat = [$decimal, $decimalPoint, $thousandSep]; } @@ -189,6 +231,7 @@ final class CoreExtension extends AbstractExtension new EmbedTokenParser(), new WithTokenParser(), new DeprecatedTokenParser(), + new GuardTokenParser(), ]; } @@ -217,19 +260,19 @@ final class CoreExtension extends AbstractExtension new TwigFilter('striptags', [self::class, 'striptags']), new TwigFilter('trim', [self::class, 'trim']), new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]), - new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecated' => '3.12', 'deprecating_package' => 'twig/twig']), + new TwigFilter('spaceless', [self::class, 'spaceless'], ['pre_escape' => 'html', 'is_safe' => ['html'], 'deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.12')]), // array helpers new TwigFilter('join', [self::class, 'join']), new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]), - new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]), + new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true, 'needs_is_sandboxed' => true]), new TwigFilter('merge', [self::class, 'merge']), new TwigFilter('batch', [self::class, 'batch']), - new TwigFilter('column', [self::class, 'column']), - new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]), - new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]), - new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]), - new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]), + new TwigFilter('column', [self::class, 'column'], ['needs_environment' => true, 'needs_is_sandboxed' => true]), + new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true, 'needs_is_sandboxed' => true]), + new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true, 'needs_is_sandboxed' => true]), + new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true, 'needs_is_sandboxed' => true]), + new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true, 'needs_is_sandboxed' => true]), // string/array filters new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]), @@ -242,6 +285,7 @@ final class CoreExtension extends AbstractExtension // iteration and runtime new TwigFilter('default', [self::class, 'default'], ['node_class' => DefaultFilter::class]), new TwigFilter('keys', [self::class, 'keys']), + new TwigFilter('invoke', [self::class, 'invoke']), ]; } @@ -259,75 +303,107 @@ final class CoreExtension extends AbstractExtension new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]), new TwigFunction('date', [$this, 'convertDate']), new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('include_only', [self::class, 'includeOnly'], ['needs_environment' => true, 'is_safe' => ['all']]), new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]), new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]), + new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]), ]; } public function getTests(): array { return [ - new TwigTest('even', null, ['node_class' => EvenTest::class]), - new TwigTest('odd', null, ['node_class' => OddTest::class]), - new TwigTest('defined', null, ['node_class' => DefinedTest::class]), - new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]), - new TwigTest('none', null, ['node_class' => NullTest::class]), - new TwigTest('null', null, ['node_class' => NullTest::class]), - new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]), + new TwigTest('even', null, ['node_class' => EvenTest::class, 'always_allowed_in_sandbox' => true]), + new TwigTest('odd', null, ['node_class' => OddTest::class, 'always_allowed_in_sandbox' => true]), + new TwigTest('defined', null, ['node_class' => DefinedTest::class, 'always_allowed_in_sandbox' => true]), + new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true, 'always_allowed_in_sandbox' => true]), + new TwigTest('none', null, ['node_class' => NullTest::class, 'always_allowed_in_sandbox' => true]), + new TwigTest('null', null, ['node_class' => NullTest::class, 'always_allowed_in_sandbox' => true]), + new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true, 'always_allowed_in_sandbox' => true]), new TwigTest('constant', null, ['node_class' => ConstantTest::class]), - new TwigTest('empty', [self::class, 'testEmpty']), - new TwigTest('iterable', 'is_iterable'), - new TwigTest('sequence', [self::class, 'testSequence']), - new TwigTest('mapping', [self::class, 'testMapping']), + new TwigTest('empty', [self::class, 'testEmpty'], ['always_allowed_in_sandbox' => true]), + new TwigTest('iterable', 'is_iterable', ['always_allowed_in_sandbox' => true]), + new TwigTest('sequence', [self::class, 'testSequence'], ['always_allowed_in_sandbox' => true]), + new TwigTest('mapping', [self::class, 'testMapping'], ['always_allowed_in_sandbox' => true]), + new TwigTest('true', null, ['node_class' => TrueTest::class, 'always_allowed_in_sandbox' => true]), ]; } public function getNodeVisitors(): array { - return [new MacroAutoImportNodeVisitor()]; + return [ + new CorrectnessNodeVisitor(), + ]; } - public function getOperators(): array + public function getExpressionParsers(): array { return [ - [ - 'not' => ['precedence' => 50, 'class' => NotUnary::class], - '-' => ['precedence' => 500, 'class' => NegUnary::class], - '+' => ['precedence' => 500, 'class' => PosUnary::class], - ], - [ - 'or' => ['precedence' => 10, 'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'and' => ['precedence' => 15, 'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'b-or' => ['precedence' => 16, 'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'b-xor' => ['precedence' => 17, 'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'b-and' => ['precedence' => 18, 'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '==' => ['precedence' => 20, 'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '!=' => ['precedence' => 20, 'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '<=>' => ['precedence' => 20, 'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '<' => ['precedence' => 20, 'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '>' => ['precedence' => 20, 'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '>=' => ['precedence' => 20, 'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '<=' => ['precedence' => 20, 'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'not in' => ['precedence' => 20, 'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'in' => ['precedence' => 20, 'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '%' => ['precedence' => 60, 'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], - '**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], - '??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], - ], + // unary operators + new UnaryOperatorExpressionParser(NotUnary::class, 'not', 50, new PrecedenceChange('twig/twig', '3.15', 70)), + new UnaryOperatorExpressionParser(SpreadUnary::class, '...', 512, description: 'Spread operator', operandPrecedence: 0), + new UnaryOperatorExpressionParser(NegUnary::class, '-', 500), + new UnaryOperatorExpressionParser(PosUnary::class, '+', 500), + + // binary operators + new BinaryOperatorExpressionParser(ElvisBinary::class, '?:', 5, InfixAssociativity::Right, description: 'Elvis operator (a ?: b)', aliases: ['? :']), + new BinaryOperatorExpressionParser(NullCoalesceBinary::class, '??', 300, InfixAssociativity::Right, new PrecedenceChange('twig/twig', '3.15', 5), description: 'Null coalescing operator (a ?? b)'), + new BinaryOperatorExpressionParser(OrBinary::class, 'or', 10), + new BinaryOperatorExpressionParser(XorBinary::class, 'xor', 12), + new BinaryOperatorExpressionParser(AndBinary::class, 'and', 15), + new BinaryOperatorExpressionParser(BitwiseOrBinary::class, 'b-or', 16), + new BinaryOperatorExpressionParser(BitwiseXorBinary::class, 'b-xor', 17), + new BinaryOperatorExpressionParser(BitwiseAndBinary::class, 'b-and', 18), + new BinaryOperatorExpressionParser(EqualBinary::class, '==', 20), + new BinaryOperatorExpressionParser(NotEqualBinary::class, '!=', 20), + new BinaryOperatorExpressionParser(SpaceshipBinary::class, '<=>', 20), + new BinaryOperatorExpressionParser(LessBinary::class, '<', 20), + new BinaryOperatorExpressionParser(GreaterBinary::class, '>', 20), + new BinaryOperatorExpressionParser(GreaterEqualBinary::class, '>=', 20), + new BinaryOperatorExpressionParser(LessEqualBinary::class, '<=', 20), + new BinaryOperatorExpressionParser(NotInBinary::class, 'not in', 20), + new BinaryOperatorExpressionParser(InBinary::class, 'in', 20), + new BinaryOperatorExpressionParser(MatchesBinary::class, 'matches', 20), + new BinaryOperatorExpressionParser(StartsWithBinary::class, 'starts with', 20), + new BinaryOperatorExpressionParser(EndsWithBinary::class, 'ends with', 20), + new BinaryOperatorExpressionParser(HasSomeBinary::class, 'has some', 20), + new BinaryOperatorExpressionParser(HasEveryBinary::class, 'has every', 20), + new BinaryOperatorExpressionParser(SameAsBinary::class, '===', 20), + new BinaryOperatorExpressionParser(NotSameAsBinary::class, '!==', 20), + new BinaryOperatorExpressionParser(RangeBinary::class, '..', 25), + new BinaryOperatorExpressionParser(AddBinary::class, '+', 30), + new BinaryOperatorExpressionParser(SubBinary::class, '-', 30), + new BinaryOperatorExpressionParser(ConcatBinary::class, '~', 40, precedenceChange: new PrecedenceChange('twig/twig', '3.15', 27)), + new BinaryOperatorExpressionParser(MulBinary::class, '*', 60), + new BinaryOperatorExpressionParser(DivBinary::class, '/', 60), + new BinaryOperatorExpressionParser(FloorDivBinary::class, '//', 60, description: 'Floor division'), + new BinaryOperatorExpressionParser(ModBinary::class, '%', 60), + new BinaryOperatorExpressionParser(PowerBinary::class, '**', 200, InfixAssociativity::Right, description: 'Exponentiation operator'), + + // ternary operator + new ConditionalTernaryExpressionParser(), + + // assignment operator + new AssignmentExpressionParser('='), + + // Twig callables + new IsExpressionParser(), + new IsNotExpressionParser(), + new FilterExpressionParser(), + new FunctionExpressionParser(), + + // get attribute operators + new DotExpressionParser(), + new SquareBracketExpressionParser(), + + // group expression + new GroupingExpressionParser(), + + // arrow function + new ArrowExpressionParser(), + + // all literals + new LiteralExpressionParser(), ]; } @@ -335,7 +411,7 @@ final class CoreExtension extends AbstractExtension * Cycles over a sequence. * * @param array|\ArrayAccess $values A non-empty sequence of values - * @param positive-int $position The position of the value to return in the cycle + * @param int<0, max> $position The position of the value to return in the cycle * * @return mixed The value at the given position in the sequence, wrapping around as needed * @@ -354,14 +430,12 @@ final class CoreExtension extends AbstractExtension trigger_deprecation('twig/twig', '3.12', 'Passing a non-countable sequence of values to "%s()" is deprecated.', __METHOD__); - return $values; + $values = self::toArray($values, false); } - - $values = self::toArray($values, false); } if (!$count = \count($values)) { - throw new RuntimeError('The "cycle" function does not work on empty sequences.'); + throw new RuntimeError('The "cycle" function expects a non-empty sequence.'); } return $values[$position % $count]; @@ -413,9 +487,7 @@ final class CoreExtension extends AbstractExtension $values = self::convertEncoding($values, 'UTF-8', $charset); } - // unicode version of str_split() - // split at all positions, but not after the start and not before the end - $values = preg_split('/(? $value) { @@ -431,7 +503,7 @@ final class CoreExtension extends AbstractExtension $values = self::toArray($values); if (0 === \count($values)) { - throw new RuntimeError('The random function cannot pick from an empty sequence/mapping.'); + throw new RuntimeError('The "random" function cannot pick from an empty sequence or mapping.'); } return $values[array_rand($values, 1)]; @@ -442,9 +514,9 @@ final class CoreExtension extends AbstractExtension * * {{ post.published_at|date("m/d/Y") }} * - * @param \DateTimeInterface|\DateInterval|string $date A date - * @param string|null $format The target format, null to use the default - * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * @param \DateTimeInterface|\DateInterval|string|int|null $date A date, a timestamp or null to use the current time + * @param string|null $format The target format, null to use the default + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged */ public function formatDate($date, $format = null, $timezone = null): string { @@ -465,8 +537,8 @@ final class CoreExtension extends AbstractExtension * * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} * - * @param \DateTimeInterface|string $date A date - * @param string $modifier A modifier string + * @param \DateTimeInterface|string|int|null $date A date, a timestamp or null to use the current time + * @param string $modifier A modifier string * * @return \DateTime|\DateTimeImmutable * @@ -481,7 +553,6 @@ final class CoreExtension extends AbstractExtension * Returns a formatted string. * * @param string|null $format - * @param ...$values * * @internal */ @@ -505,8 +576,8 @@ final class CoreExtension extends AbstractExtension * {# do something #} * {% endif %} * - * @param \DateTimeInterface|string|null $date A date or null to use the current time - * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * @param \DateTimeInterface|string|int|null $date A date, a timestamp or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged * * @return \DateTime|\DateTimeImmutable */ @@ -544,10 +615,10 @@ final class CoreExtension extends AbstractExtension } $asString = (string) $date; - if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + if (ctype_digit($asString) || ('' !== $asString && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { $date = new \DateTime('@'.$date); } else { - $date = new \DateTime($date, $this->getTimezone()); + $date = new \DateTime($date); } if (false !== $timezone) { @@ -568,7 +639,7 @@ final class CoreExtension extends AbstractExtension public static function replace($str, $from): string { if (!is_iterable($from)) { - throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); + throw new RuntimeError(\sprintf('The "replace" filter expects a sequence or a mapping, got "%s".', get_debug_type($from))); } return strtr($str ?? '', self::toArray($from)); @@ -577,11 +648,11 @@ final class CoreExtension extends AbstractExtension /** * Rounds a number. * - * @param int|float|string|null $value The value to round - * @param int|float $precision The rounding precision - * @param string $method The method to use for rounding + * @param int|float|string|null $value The value to round + * @param int|float $precision The rounding precision + * @param 'common'|'ceil'|'floor' $method The method to use for rounding * - * @return int|float The rounded number + * @return float The rounded number * * @internal */ @@ -594,7 +665,7 @@ final class CoreExtension extends AbstractExtension } if ('ceil' !== $method && 'floor' !== $method) { - throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); + throw new RuntimeError('The "round" filter only supports the "common", "ceil", and "floor" methods.'); } return $method($value * 10 ** $precision) / 10 ** $precision; @@ -665,7 +736,7 @@ final class CoreExtension extends AbstractExtension foreach ($arrays as $argNumber => $array) { if (!is_iterable($array)) { - throw new RuntimeError(\sprintf('The merge filter only works with sequences/mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); + throw new RuntimeError(\sprintf('The "merge" filter expects a sequence or a mapping, got "%s" for argument %d.', get_debug_type($array), $argNumber + 1)); } $result = array_merge($result, self::toArray($array)); @@ -757,9 +828,9 @@ final class CoreExtension extends AbstractExtension * {{ [1, 2, 3]|join }} * {# returns 123 #} * - * @param array $value An array - * @param string $glue The separator - * @param string|null $and The separator for the last pair + * @param iterable|array|string|float|int|bool|null $value An array + * @param string $glue The separator + * @param string|null $and The separator for the last pair * * @internal */ @@ -816,7 +887,7 @@ final class CoreExtension extends AbstractExtension } if ($limit <= 1) { - return preg_split('/(? (int) $bTrim; - } else { - return (float) $a <=> (float) $bTrim; } + + return (float) $a <=> (float) $bTrim; } if (\is_string($a) && \is_int($b)) { $aTrim = trim($a, " \t\n\r\v\f"); @@ -1062,9 +1140,9 @@ final class CoreExtension extends AbstractExtension } if ((int) $aTrim == $aTrim) { return (int) $aTrim <=> $b; - } else { - return (float) $aTrim <=> (float) $b; } + + return (float) $aTrim <=> (float) $b; } // float <=> string @@ -1096,17 +1174,21 @@ final class CoreExtension extends AbstractExtension } /** - * @throws RuntimeError When an invalid pattern is used + * @throws RuntimeError When the regular expression cannot be evaluated * * @internal */ public static function matches(string $regexp, ?string $str): int { - set_error_handler(function ($t, $m) use ($regexp) { + set_error_handler(static function ($t, $m) use ($regexp) { throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); }); try { - return preg_match($regexp, $str ?? ''); + if (false === $result = preg_match($regexp, $str ?? '')) { + throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" failed: %s.', $regexp, preg_last_error_msg())); + } + + return $result; } finally { restore_error_handler(); } @@ -1115,30 +1197,29 @@ final class CoreExtension extends AbstractExtension /** * Returns a trimmed string. * - * @param string|null $string - * @param string|null $characterMask - * @param string $side + * @param string|\Stringable|null $string + * @param string|null $characterMask + * @param string $side left, right, or both * - * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + * @throws RuntimeError When an invalid trimming side is used * * @internal */ - public static function trim($string, $characterMask = null, $side = 'both'): string + public static function trim($string, $characterMask = null, $side = 'both'): string|\Stringable { if (null === $characterMask) { - $characterMask = " \t\n\r\0\x0B"; + $characterMask = self::DEFAULT_TRIM_CHARS; } - switch ($side) { - case 'both': - return trim($string ?? '', $characterMask); - case 'left': - return ltrim($string ?? '', $characterMask); - case 'right': - return rtrim($string ?? '', $characterMask); - default: - throw new RuntimeError('Trimming side must be "left", "right" or "both".'); - } + $trimmed = match ($side) { + 'both' => trim($string ?? '', $characterMask), + 'left' => ltrim($string ?? '', $characterMask), + 'right' => rtrim($string ?? '', $characterMask), + default => throw new RuntimeError('Trimming side must be "left", "right" or "both".'), + }; + + // trimming a safe string with the default character mask always returns a safe string (independently of the context) + return $string instanceof Markup && self::DEFAULT_TRIM_CHARS === $characterMask ? new Markup($trimmed, $string->getCharset()) : $trimmed; } /** @@ -1181,6 +1262,22 @@ final class CoreExtension extends AbstractExtension return iconv($from, $to, $string ?? ''); } + /** + * Unicode version of str_split(), an empty string giving a single empty character. + * + * @return non-empty-list + * + * @throws RuntimeError When the string cannot be split into characters + */ + private static function splitIntoCharacters(string $string, string $name): array + { + if (false === preg_match_all('/./us', $string, $matches)) { + throw new RuntimeError(\sprintf('Unable to split the string passed to "%s" into characters: %s.', $name, preg_last_error_msg())); + } + + return $matches[0] ?: ['']; + } + /** * Returns the length of a variable. * @@ -1276,21 +1373,12 @@ final class CoreExtension extends AbstractExtension /** * @internal + * + * to be removed in 4.0 */ - public static function callMacro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) + public static function callMacro(MacroNamespace $namespace, string $method, array $args, int $lineno, array $context, Source $source) { - if (!method_exists($template, $method)) { - $parent = $template; - while ($parent = $parent->getParent($context)) { - if (method_exists($parent, $method)) { - return $parent->$method(...$args); - } - } - - throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); - } - - return $template->$method(...$args); + return $namespace->call(substr($method, \strlen('macro_')), $args, $context, $lineno, $source); } /** @@ -1327,6 +1415,39 @@ final class CoreExtension extends AbstractExtension return $preserveKeys ? $seq : array_values($seq); } + /** + * @param list $names + * + * @internal + */ + public static function destructureSequence(array &$context, array $names, \Traversable $sequence): \Traversable + { + $count = \count($names); + if (0 === $count) { + return $sequence; + } + + $i = 0; + foreach ($sequence as $value) { + $name = $names[$i]; + if (null !== $name) { + $context[$name] = $value; + } + if (++$i === $count) { + return $sequence; + } + } + + for (; $i < $count; ++$i) { + $name = $names[$i]; + if (null !== $name) { + $context[$name] = null; + } + } + + return $sequence; + } + /** * Checks if a variable is empty. * @@ -1364,8 +1485,6 @@ final class CoreExtension extends AbstractExtension * {# ... #} * {% endif %} * - * @param mixed $value - * * @internal */ public static function testSequence($value): bool @@ -1389,8 +1508,6 @@ final class CoreExtension extends AbstractExtension * {# ... #} * {% endif %} * - * @param mixed $value - * * @internal */ public static function testMapping($value): bool @@ -1416,10 +1533,20 @@ final class CoreExtension extends AbstractExtension * @param bool $ignoreMissing Whether to ignore missing templates or not * @param bool $sandboxed Whether to sandbox the template or not * + * @return string|Markup + * * @internal */ - public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false): string + public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) { + if (\func_num_args() >= 7) { + if ($sandboxed) { + trigger_deprecation('twig/twig', '3.29', 'The "sandboxed" argument of the "include" function is deprecated, use the "render_sandboxed" function to render untrusted templates instead.'); + } else { + trigger_deprecation('twig/twig', '3.29', 'The "sandboxed" argument of the "include" function is deprecated, remove the argument as "false" has no effect.'); + } + } + $alreadySandboxed = false; $sandbox = null; if ($withContext) { @@ -1427,9 +1554,9 @@ final class CoreExtension extends AbstractExtension } if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { - $sandbox = $env->getExtension(SandboxExtension::class); + $sandbox = $env->getExtension(SandboxExtension::class)->getChecker(); if (!$alreadySandboxed = $sandbox->isSandboxed()) { - $sandbox->enableSandbox(); + $sandbox->setSandboxed(true); } } @@ -1445,18 +1572,32 @@ final class CoreExtension extends AbstractExtension return ''; } - if ($isSandboxed) { - $loaded->unwrap()->checkSecurity(); - } + $rendered = $loaded->render($variables); - return $loaded->render($variables); + return '' === $rendered ? '' : new Markup($rendered, $env->getCharset()); } finally { if ($isSandboxed && !$alreadySandboxed) { - $sandbox->disableSandbox(); + $sandbox->setSandboxed(false); } } } + /** + * Renders a template without giving it access to the current context. + * + * @param string|array|TemplateWrapper $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @return string|Markup + * + * @internal + */ + public static function includeOnly(Environment $env, $template, array $variables = [], bool $ignoreMissing = false) + { + return self::include($env, [], $template, $variables, false, $ignoreMissing); + } + /** * Returns a template content without rendering it. * @@ -1499,6 +1640,30 @@ final class CoreExtension extends AbstractExtension return $enum::cases(); } + /** + * Provides the ability to access enums by their class names. + * + * @template T of \UnitEnum + * + * @param class-string $enum + * + * @return T + * + * @internal + */ + public static function enum(string $enum): \UnitEnum + { + if (!enum_exists($enum)) { + throw new RuntimeError(\sprintf('"%s" is not an enum.', $enum)); + } + + if (!$cases = $enum::cases()) { + throw new RuntimeError(\sprintf('"%s" is an empty enum.', $enum)); + } + + return $cases[0]; + } + /** * Provides the ability to get constants from instances as well as class/global constants. * @@ -1516,10 +1681,10 @@ final class CoreExtension extends AbstractExtension { if (null !== $object) { if ('class' === $constant) { - return $checkDefined ? true : \get_class($object); + return $checkDefined ? true : $object::class; } - $constant = \get_class($object).'::'.$constant; + $constant = $object::class.'::'.$constant; } if (!\defined($constant)) { @@ -1528,7 +1693,7 @@ final class CoreExtension extends AbstractExtension } if ('::class' === strtolower(substr($constant, -7))) { - throw new RuntimeError(\sprintf('You cannot use the Twig function "constant()" to access "%s". You could provide an object and call constant("class", $object) or use the class name directly as a string.', $constant)); + throw new RuntimeError(\sprintf('You cannot use the Twig function "constant" to access "%s". You could provide an object and call constant("class", $object) or use the class name directly as a string.', $constant)); } throw new RuntimeError(\sprintf('Constant "%s" is undefined.', $constant)); @@ -1549,7 +1714,7 @@ final class CoreExtension extends AbstractExtension public static function batch($items, $size, $fill = null, $preserveKeys = true): array { if (!is_iterable($items)) { - throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); + throw new RuntimeError(\sprintf('The "batch" filter expects a sequence or a mapping, got "%s".', get_debug_type($items))); } $size = (int) ceil($size); @@ -1587,13 +1752,36 @@ final class CoreExtension extends AbstractExtension */ public static function getAttribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) { + $propertyNotAllowedError = null; + if ($sandboxed && $item instanceof \Stringable) { + $env->getExtension(SandboxExtension::class)->getChecker()->ensureToStringAllowed($item, $lineno, $source); + } + // array if (Template::METHOD_CALL !== $type) { $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; - if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) - || ($object instanceof \ArrayAccess && isset($object[$arrayItem])) - ) { + if ($arrayItem instanceof \Stringable && ($object instanceof \ArrayObject || $object instanceof \ArrayIterator)) { + $arrayItem = (string) $arrayItem; + } + + if ($sandboxed && $object instanceof \ArrayAccess && !\in_array($object::class, self::ARRAY_LIKE_CLASSES, true)) { + try { + $env->getExtension(SandboxExtension::class)->getChecker()->checkPropertyAllowed($object, $arrayItem, $lineno, $source); + } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) { + // The methodCheck path expects $item to be a string; stringify it here + // to avoid PHP 8.1+ implicit float-to-int deprecations on downstream + // array key lookups (e.g. isset($cache[$class][$item])). + $item = (string) $item; + goto methodCheck; + } + } + + if (match (true) { + \is_array($object) => \array_key_exists($arrayItem = (string) $arrayItem, $object), + $object instanceof \ArrayAccess => $object->offsetExists($arrayItem), + default => false, + }) { if ($isDefinedTest) { return true; } @@ -1611,11 +1799,15 @@ final class CoreExtension extends AbstractExtension } if ($object instanceof \ArrayAccess) { - $message = \sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); + if (\is_object($arrayItem) || \is_array($arrayItem)) { + $message = \sprintf('Key of type "%s" does not exist in ArrayAccess-able object of class "%s".', get_debug_type($arrayItem), get_debug_type($object)); + } else { + $message = \sprintf('Key "%s" does not exist in ArrayAccess-able object of class "%s".', $arrayItem, get_debug_type($object)); + } } elseif (\is_object($object)) { - $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, get_debug_type($object)); } elseif (\is_array($object)) { - if (empty($object)) { + if (!$object) { $message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.', $arrayItem); } else { $message = \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); @@ -1624,18 +1816,20 @@ final class CoreExtension extends AbstractExtension if (null === $object) { $message = \sprintf('Impossible to access a key ("%s") on a null variable.', $item); } else { - $message = \sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + $message = \sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, get_debug_type($object), $object); } } elseif (null === $object) { $message = \sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); } else { - $message = \sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + $message = \sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, get_debug_type($object), $object); } throw new RuntimeError($message, $lineno, $source); } } + $item = (string) $item; + if (!\is_object($object)) { if ($isDefinedTest) { return false; @@ -1650,7 +1844,7 @@ final class CoreExtension extends AbstractExtension } elseif (\is_array($object)) { $message = \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.', $item); } else { - $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, get_debug_type($object), $object); } throw new RuntimeError($message, $lineno, $source); @@ -1662,52 +1856,84 @@ final class CoreExtension extends AbstractExtension // object property if (Template::METHOD_CALL !== $type) { - if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + if ($sandboxed) { + try { + $env->getExtension(SandboxExtension::class)->getChecker()->checkPropertyAllowed($object, $item, $lineno, $source); + } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) { + goto methodCheck; + } + } + + static $propertyCheckers = []; + + if (isset($object->$item) + || ($propertyCheckers[$object::class][$item] ??= self::getPropertyChecker($object::class, $item))($object, $item) + ) { if ($isDefinedTest) { return true; } - if ($sandboxed) { - $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); + return $object->$item; + } + + if ($object instanceof \DateTimeInterface && \in_array($item, ['date', 'timezone', 'timezone_type'], true)) { + if ($isDefinedTest) { + return true; } - return $object->$item; + return ((array) $object)[$item]; + } + + if (\defined($object::class.'::'.$item)) { + if ($isDefinedTest) { + return true; + } + + return \constant($object::class.'::'.$item); } } + methodCheck: + static $cache = []; - $class = \get_class($object); + $class = $object::class; // object method // precedence: getXxx() > isXxx() > hasXxx() if (!isset($cache[$class])) { $methods = get_class_methods($object); + if ($object instanceof \Closure) { + $methods[] = '__invoke'; + } sort($methods); - $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); + $lcMethods = array_map('strtolower', $methods); $classCache = []; foreach ($methods as $i => $method) { $classCache[$method] = $method; $classCache[$lcName = $lcMethods[$i]] = $method; if ('g' === $lcName[0] && str_starts_with($lcName, 'get')) { - $name = substr($method, 3); - $lcName = substr($lcName, 3); + $prefixLength = 3; + $lcName = substr($lcName, $prefixLength); } elseif ('i' === $lcName[0] && str_starts_with($lcName, 'is')) { - $name = substr($method, 2); - $lcName = substr($lcName, 2); + $prefixLength = 2; + $lcName = substr($lcName, $prefixLength); } elseif ('h' === $lcName[0] && str_starts_with($lcName, 'has')) { - $name = substr($method, 3); - $lcName = substr($lcName, 3); - if (\in_array('is'.$lcName, $lcMethods)) { + $prefixLength = 3; + $lcName = substr($lcName, $prefixLength); + if (\in_array('is'.$lcName, $lcMethods, true)) { continue; } } else { continue; } - // skip get() and is() methods (in which case, $name is empty) - if ($name) { + // skip get(), is() and has() methods (in which case, $lcName is empty) + if ($lcName) { + // camelCase name (e.g. getFooBar() -> fooBar) + $name = $lcName[0].substr($method, $prefixLength + 1); + if (!isset($classCache[$name])) { $classCache[$name] = $method; } @@ -1723,7 +1949,7 @@ final class CoreExtension extends AbstractExtension $call = false; if (isset($cache[$class][$item])) { $method = $cache[$class][$item]; - } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { + } elseif (isset($cache[$class][$lcItem = strtolower($item)])) { $method = $cache[$class][$lcItem]; } elseif (isset($cache[$class]['__call'])) { $method = $item; @@ -1733,21 +1959,37 @@ final class CoreExtension extends AbstractExtension return false; } + if ($propertyNotAllowedError) { + throw $propertyNotAllowedError; + } + if ($ignoreStrictCheck || !$env->isStrictVariables()) { return; } - throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); + throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()", "is%1$s()", "has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); + } + + if ($sandboxed) { + try { + $env->getExtension(SandboxExtension::class)->getChecker()->checkMethodAllowed($object, $method, $lineno, $source); + } catch (SecurityNotAllowedMethodError $e) { + if ($isDefinedTest) { + return false; + } + + if ($propertyNotAllowedError) { + throw $propertyNotAllowedError; + } + + throw $e; + } } if ($isDefinedTest) { return true; } - if ($sandboxed) { - $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); - } - // Some objects throw exceptions when they have __call, and the method we try // to call is not supported. If ignoreStrictCheck is true, we should return null. try { @@ -1781,27 +2023,46 @@ final class CoreExtension extends AbstractExtension * * @internal */ - public static function column($array, $name, $index = null): array + public static function column(Environment $env, bool $isSandboxed, $array, $name, $index = null): array { + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "column" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); + } + if ($array instanceof \Traversable) { $array = iterator_to_array($array); - } elseif (!\is_array($array)) { - throw new RuntimeError(\sprintf('The column filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + if ($isSandboxed) { + // The sandbox might be enabled via a SourcePolicyInterface, in which case the SandboxExtension + // would not consider the sandbox active without the current Source: $isSandboxed is already + // computed against the call-site source, so check the policy directly to honor that decision. + $policy = $env->getExtension(SandboxExtension::class)->getChecker()->getSecurityPolicy(); + foreach ($array as $item) { + if (\is_object($item)) { + $policy->checkPropertyAllowed($item, (string) $name); + if (null !== $index) { + $policy->checkPropertyAllowed($item, (string) $index); + } + } + } } return array_column($array, $name, $index); } /** + * @param \Closure $arrow + * * @internal */ - public static function filter(Environment $env, $array, $arrow) + public static function filter(Environment $env, bool $isSandboxed, $array, $arrow) { if (!is_iterable($array)) { - throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); + throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'filter', 'filter'); + self::checkArrow($isSandboxed, $arrow, 'filter', 'filter'); if (\is_array($array)) { return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); @@ -1812,11 +2073,17 @@ final class CoreExtension extends AbstractExtension } /** + * @param \Closure $arrow + * * @internal */ - public static function find(Environment $env, $array, $arrow) + public static function find(Environment $env, bool $isSandboxed, $array, $arrow) { - self::checkArrowInSandbox($env, $arrow, 'find', 'filter'); + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "find" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); + } + + self::checkArrow($isSandboxed, $arrow, 'find', 'filter'); foreach ($array as $k => $v) { if ($arrow($v, $k)) { @@ -1828,15 +2095,17 @@ final class CoreExtension extends AbstractExtension } /** + * @param \Closure $arrow + * * @internal */ - public static function map(Environment $env, $array, $arrow) + public static function map(Environment $env, bool $isSandboxed, $array, $arrow) { if (!is_iterable($array)) { - throw new RuntimeError(\sprintf('The "map" filter expects a sequence/mapping or "Traversable", got "%s".', get_debug_type($array))); + throw new RuntimeError(\sprintf('The "map" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); } - self::checkArrowInSandbox($env, $arrow, 'map', 'filter'); + self::checkArrow($isSandboxed, $arrow, 'map', 'filter'); $r = []; foreach ($array as $k => $v) { @@ -1847,16 +2116,18 @@ final class CoreExtension extends AbstractExtension } /** + * @param \Closure $arrow + * * @internal */ - public static function reduce(Environment $env, $array, $arrow, $initial = null) + public static function reduce(Environment $env, bool $isSandboxed, $array, $arrow, $initial = null) { - self::checkArrowInSandbox($env, $arrow, 'reduce', 'filter'); - - if (!\is_array($array) && !$array instanceof \Traversable) { - throw new RuntimeError(\sprintf('The "reduce" filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array))); + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "reduce" filter expects a sequence or a mapping, got "%s".', get_debug_type($array))); } + self::checkArrow($isSandboxed, $arrow, 'reduce', 'filter'); + $accumulator = $initial; foreach ($array as $key => $value) { $accumulator = $arrow($accumulator, $value, $key); @@ -1866,11 +2137,17 @@ final class CoreExtension extends AbstractExtension } /** + * @param \Closure $arrow + * * @internal */ - public static function arraySome(Environment $env, $array, $arrow) + public static function arraySome(Environment $env, $array, $arrow, bool $isSandboxed = false) { - self::checkArrowInSandbox($env, $arrow, 'has some', 'operator'); + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "has some" test expects a sequence or a mapping, got "%s".', get_debug_type($array))); + } + + self::checkArrow($isSandboxed, $arrow, 'has some', 'operator'); foreach ($array as $k => $v) { if ($arrow($v, $k)) { @@ -1882,11 +2159,17 @@ final class CoreExtension extends AbstractExtension } /** + * @param \Closure $arrow + * * @internal */ - public static function arrayEvery(Environment $env, $array, $arrow) + public static function arrayEvery(Environment $env, $array, $arrow, bool $isSandboxed = false) { - self::checkArrowInSandbox($env, $arrow, 'has every', 'operator'); + if (!is_iterable($array)) { + throw new RuntimeError(\sprintf('The "has every" test expects a sequence or a mapping, got "%s".', get_debug_type($array))); + } + + self::checkArrow($isSandboxed, $arrow, 'has every', 'operator'); foreach ($array as $k => $v) { if (!$arrow($v, $k)) { @@ -1900,11 +2183,17 @@ final class CoreExtension extends AbstractExtension /** * @internal */ - public static function checkArrowInSandbox(Environment $env, $arrow, $thing, $type) + public static function checkArrow(bool $isSandboxed, $arrow, $thing, $type): void { - if (!$arrow instanceof \Closure && $env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) { + if ($arrow instanceof \Closure) { + return; + } + + if ($isSandboxed) { throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); } + + trigger_deprecation('twig/twig', '3.15', 'Passing a callable that is not a PHP \Closure as an argument to the "%s" %s is deprecated.', $thing, $type); } /** @@ -1951,7 +2240,7 @@ final class CoreExtension extends AbstractExtension */ public static function parseBlockFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression { - $fakeFunction = new TwigFunction('block', fn ($name, $template = null) => null); + $fakeFunction = new TwigFunction('block', static fn ($name, $template = null) => null); $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args); return new BlockReferenceExpression($args[0], $args[1] ?? null, $line); @@ -1962,9 +2251,41 @@ final class CoreExtension extends AbstractExtension */ public static function parseAttributeFunction(Parser $parser, Node $fakeNode, $args, int $line): AbstractExpression { - $fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = null) => null); + $fakeFunction = new TwigFunction('attribute', static fn ($variable, $attribute, $arguments = null) => null); $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($args); + /* + Deprecation to uncomment sometimes during the lifetime of the 4.x branch + $src = $parser->getStream()->getSourceContext(); + $dep = new DeprecatedCallableInfo('twig/twig', '3.15', 'The "attribute" function is deprecated, use the "." notation instead.'); + $dep->setName('attribute'); + $dep->setType('function'); + $dep->triggerDeprecation($src->getPath() ?: $src->getName(), $line); + */ + return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line); } + + private static function getPropertyChecker(string $class, string $property): \Closure + { + static $classReflectors = []; + + $class = $classReflectors[$class] ??= new \ReflectionClass($class); + + if (!$class->hasProperty($property)) { + static $propertyExists; + + return $propertyExists ??= \Closure::fromCallable('property_exists'); + } + + $property = $class->getProperty($property); + + if (!$property->isPublic() || $property->isStatic()) { + static $false; + + return $false ??= static fn () => false; + } + + return static fn ($object) => $property->isInitialized($object); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php index 52531c436..f044dffff 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -14,6 +14,7 @@ namespace Twig\Extension; use Twig\Environment; use Twig\FileExtensionEscapingStrategy; use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\Filter\EscapeFilter; use Twig\Node\Expression\Filter\RawFilter; use Twig\Node\Node; use Twig\NodeVisitor\EscaperNodeVisitor; @@ -51,12 +52,20 @@ final class EscaperExtension extends AbstractExtension public function getFilters(): array { return [ - new TwigFilter('escape', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]), - new TwigFilter('e', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]), + new TwigFilter('escape', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe'], 'node_class' => EscapeFilter::class]), + new TwigFilter('e', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe'], 'node_class' => EscapeFilter::class]), new TwigFilter('raw', null, ['is_safe' => ['all'], 'node_class' => RawFilter::class]), ]; } + public function getLastModified(): int + { + return max( + parent::getLastModified(), + filemtime((new \ReflectionClass(EscaperRuntime::class))->getFileName()), + ); + } + /** * @deprecated since Twig 3.10 */ @@ -68,13 +77,13 @@ final class EscaperExtension extends AbstractExtension } $this->environment = $environment; - $this->escaper = $environment->getRuntime(EscaperRuntime::class); + $this->escaper = null; } /** * @deprecated since Twig 3.10 */ - public function setEscaperRuntime(EscaperRuntime $escaper) + public function setEscaperRuntime(EscaperRuntime $escaper): void { trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__); @@ -124,20 +133,18 @@ final class EscaperExtension extends AbstractExtension * * @deprecated since Twig 3.10 */ - public function setEscaper($strategy, callable $callable) + public function setEscaper($strategy, callable $callable): void { trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setEscaper()" method instead (be warned that Environment is not passed anymore to the callable).', __METHOD__); - if (!isset($this->environment)) { - throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); - } + $escaper = $this->getEscaper(__METHOD__); $this->escapers[$strategy] = $callable; $callable = function ($string, $charset) use ($callable) { return $callable($this->environment, $string, $charset); }; - $this->escaper->setEscaper($strategy, $callable); + $escaper->setEscaper($strategy, $callable); } /** @@ -157,33 +164,40 @@ final class EscaperExtension extends AbstractExtension /** * @deprecated since Twig 3.10 */ - public function setSafeClasses(array $safeClasses = []) + public function setSafeClasses(array $safeClasses = []): void { trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setSafeClasses()" method instead.', __METHOD__); - if (!isset($this->escaper)) { - throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); - } - - $this->escaper->setSafeClasses($safeClasses); + $this->getEscaper(__METHOD__)->setSafeClasses($safeClasses); } /** * @deprecated since Twig 3.10 */ - public function addSafeClass(string $class, array $strategies) + public function addSafeClass(string $class, array $strategies): void { trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::addSafeClass()" method instead.', __METHOD__); - if (!isset($this->escaper)) { - throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__)); + $this->getEscaper(__METHOD__)->addSafeClass($class, $strategies); + } + + private function getEscaper(string $fromMethod): EscaperRuntime + { + if (isset($this->escaper)) { + return $this->escaper; } - $this->escaper->addSafeClass($class, $strategies); + if (isset($this->environment)) { + return $this->escaper = $this->environment->getRuntime(EscaperRuntime::class); + } + + throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', $fromMethod)); } /** * @internal + * + * @return array */ public static function escapeFilterIsSafe(Node $filterArgs) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php index 10a42b6b1..60ca35b4e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/ExtensionInterface.php @@ -12,7 +12,10 @@ namespace Twig\Extension; use Twig\ExpressionParser; -use Twig\Node\Expression\AbstractExpression; +use Twig\ExpressionParser\ExpressionParserInterface; +use Twig\ExpressionParser\PrecedenceChange; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Unary\AbstractUnary; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; use Twig\TwigFilter; @@ -23,6 +26,8 @@ use Twig\TwigTest; * Interface implemented by extension classes. * * @author Fabien Potencier + * + * @method array getExpressionParsers() */ interface ExtensionInterface { @@ -64,11 +69,11 @@ interface ExtensionInterface /** * Returns a list of operators to add to the existing list. * - * @return array First array of unary operators, second array of binary operators + * @return array * * @psalm-return array{ - * array}>, - * array, associativity: ExpressionParser::OPERATOR_*}> + * array}>, + * array, associativity: ExpressionParser::OPERATOR_*}> * } */ public function getOperators(); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/LastModifiedExtensionInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/LastModifiedExtensionInterface.php new file mode 100644 index 000000000..4bab0c07c --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/LastModifiedExtensionInterface.php @@ -0,0 +1,23 @@ + + */ +final class SandboxBridgeExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('render_sandboxed', [SandboxBridgeRuntime::class, 'render'], ['is_safe_callback' => [RenderSandboxedFunction::class, 'getSafe'], 'node_class' => RenderSandboxedFunction::class]), + ]; + } + + public function getLastModified(): int + { + return max( + parent::getLastModified(), + filemtime((new \ReflectionClass(SandboxBridgeRuntime::class))->getFileName()), + ); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php index 4e96760f7..7b4f96fe0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/SandboxExtension.php @@ -12,25 +12,31 @@ namespace Twig\Extension; use Twig\NodeVisitor\SandboxNodeVisitor; +use Twig\Sandbox\Sandbox; +use Twig\Sandbox\SecurityChecker; use Twig\Sandbox\SecurityNotAllowedMethodError; -use Twig\Sandbox\SecurityNotAllowedPropertyError; use Twig\Sandbox\SecurityPolicyInterface; use Twig\Sandbox\SourcePolicyInterface; use Twig\Source; use Twig\TokenParser\SandboxTokenParser; +/** + * This extension is the wiring behind "Twig\Sandbox\Sandbox" and should not + * be used directly, use a "Sandbox" to render untrusted templates instead. + * + * @internal since Twig 3.29 + */ final class SandboxExtension extends AbstractExtension { - private $sandboxedGlobally; - private $sandboxed; - private $policy; - private $sourcePolicy; + private SecurityChecker $checker; public function __construct(SecurityPolicyInterface $policy, $sandboxed = false, ?SourcePolicyInterface $sourcePolicy = null) { - $this->policy = $policy; - $this->sandboxedGlobally = $sandboxed; - $this->sourcePolicy = $sourcePolicy; + if (null !== $sourcePolicy) { + trigger_deprecation('twig/twig', '3.27.0', 'The "%s" interface is deprecated with no replacement, do not pass an instance to "%s".', SourcePolicyInterface::class, self::class); + } + + $this->checker = new SecurityChecker($policy, (bool) $sandboxed, $sourcePolicy); } public function getTokenParsers(): array @@ -43,93 +49,99 @@ final class SandboxExtension extends AbstractExtension return [new SandboxNodeVisitor()]; } - public function enableSandbox(): void + /** + * @internal + */ + public function getChecker(): SecurityChecker { - $this->sandboxed = true; + return $this->checker; } + /** + * @deprecated since Twig 3.29, use "Twig\Sandbox\Sandbox" to render untrusted templates instead + */ + public function enableSandbox(): void + { + trigger_deprecation('twig/twig', '3.29', 'The "%s()" method is deprecated, use "%s" to render untrusted templates instead.', __METHOD__, Sandbox::class); + + $this->checker->setSandboxed(true); + } + + /** + * @deprecated since Twig 3.29, use "Twig\Sandbox\Sandbox" to render untrusted templates instead + */ public function disableSandbox(): void { - $this->sandboxed = false; + trigger_deprecation('twig/twig', '3.29', 'The "%s()" method is deprecated, use "%s" to render untrusted templates instead.', __METHOD__, Sandbox::class); + + $this->checker->setSandboxed(false); } public function isSandboxed(?Source $source = null): bool { - return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source); + return $this->checker->isSandboxed($source); } + /** + * @deprecated since Twig 3.29, use "Twig\Sandbox\Sandbox" to render untrusted templates instead + */ public function isSandboxedGlobally(): bool { - return $this->sandboxedGlobally; + trigger_deprecation('twig/twig', '3.29', 'The "%s()" method is deprecated, use "%s" to render untrusted templates instead.', __METHOD__, Sandbox::class); + + return $this->checker->isSandboxedGlobally(); } - private function isSourceSandboxed(?Source $source): bool + public function setSecurityPolicy(SecurityPolicyInterface $policy): void { - if (null === $source || null === $this->sourcePolicy) { - return false; - } - - return $this->sourcePolicy->enableSandbox($source); - } - - public function setSecurityPolicy(SecurityPolicyInterface $policy) - { - $this->policy = $policy; + $this->checker->setSecurityPolicy($policy); } public function getSecurityPolicy(): SecurityPolicyInterface { - return $this->policy; + return $this->checker->getSecurityPolicy(); } - public function checkSecurity($tags, $filters, $functions, ?Source $source = null): void + public function checkSecurity($tags, $filters, $functions, $tests = [], $source = null): void { - if ($this->isSandboxed($source)) { - $this->policy->checkSecurity($tags, $filters, $functions); + // BC: previous signature was checkSecurity($tags, $filters, $functions, ?Source $source = null); + // detect a legacy call where the 4th positional argument was the Source. + if ($tests instanceof Source || (null === $tests && \func_num_args() < 5)) { + trigger_deprecation('twig/twig', '3.28', 'Passing a "Twig\Source" as the 4th argument of "%s()" is deprecated; pass an array of tests instead.', __METHOD__); + $source = $tests; + $tests = []; } + + $this->checker->checkSecurity($tags, $filters, $functions, $tests, $source); } public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null): void { - if ($this->isSandboxed($source)) { - try { - $this->policy->checkMethodAllowed($obj, $method); - } catch (SecurityNotAllowedMethodError $e) { - $e->setSourceContext($source); - $e->setTemplateLine($lineno); - - throw $e; - } - } + $this->checker->checkMethodAllowed($obj, $method, $lineno, $source); } public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source $source = null): void { - if ($this->isSandboxed($source)) { - try { - $this->policy->checkPropertyAllowed($obj, $property); - } catch (SecurityNotAllowedPropertyError $e) { - $e->setSourceContext($source); - $e->setTemplateLine($lineno); - - throw $e; - } - } + $this->checker->checkPropertyAllowed($obj, $property, $lineno, $source); } + /** + * @throws SecurityNotAllowedMethodError + */ public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null) { - if ($this->isSandboxed($source) && $obj instanceof \Stringable) { - try { - $this->policy->checkMethodAllowed($obj, '__toString'); - } catch (SecurityNotAllowedMethodError $e) { - $e->setSourceContext($source); - $e->setTemplateLine($lineno); + return $this->checker->ensureToStringAllowed($obj, $lineno, $source); + } - throw $e; - } - } - - return $obj; + /** + * Materialises a spread operand and runs the policy on every element. + * + * @internal + * + * @throws SecurityNotAllowedMethodError + */ + public function ensureSpreadAllowed(iterable $obj, int $lineno = -1, ?Source $source = null): array + { + return $this->checker->ensureSpreadAllowed($obj, $lineno, $source); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php b/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php index 698d181f1..da5b2dae3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Extension/StringLoaderExtension.php @@ -29,6 +29,9 @@ final class StringLoaderExtension extends AbstractExtension * * {{ include(template_from_string("Hello {{ name }}")) }} * + * Never expose `template_from_string` to untrusted template + * authors (like in a sandboxed environment). See the docs for more details. + * * @param string|null $name An optional name of the template to be used in error messages * * @internal diff --git a/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php b/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php index 28d57a41c..450093466 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php +++ b/data/web/inc/lib/vendor/twig/twig/src/ExtensionSet.php @@ -12,15 +12,25 @@ namespace Twig; use Twig\Error\RuntimeError; +use Twig\ExpressionParser\ExpressionParsers; +use Twig\ExpressionParser\Infix\BinaryOperatorExpressionParser; +use Twig\ExpressionParser\InfixAssociativity; +use Twig\ExpressionParser\InfixExpressionParserInterface; +use Twig\ExpressionParser\PrecedenceChange; +use Twig\ExpressionParser\Prefix\UnaryOperatorExpressionParser; +use Twig\Extension\AttributeExtension; use Twig\Extension\ExtensionInterface; use Twig\Extension\GlobalsInterface; +use Twig\Extension\LastModifiedExtensionInterface; use Twig\Extension\StagingExtension; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\Binary\AbstractBinary; -use Twig\Node\Expression\Unary\AbstractUnary; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; +// Help opcache.preload discover always-needed symbols +// @see https://github.com/php/php-src/issues/10131 +class_exists(BinaryOperatorExpressionParser::class); + /** * @author Fabien Potencier * @@ -46,14 +56,16 @@ final class ExtensionSet private $functions; /** @var array */ private $dynamicFunctions; - /** @var array}> */ - private $unaryOperators; - /** @var array, associativity: ExpressionParser::OPERATOR_*}> */ - private $binaryOperators; - /** @var array */ + private ExpressionParsers $expressionParsers; + /** @var array|null */ private $globals; + /** @var array */ private $functionCallbacks = []; + /** @var array */ private $filterCallbacks = []; + /** @var array */ + private $testCallbacks = []; + /** @var array */ private $parserCallbacks = []; private $lastModified = 0; @@ -62,7 +74,7 @@ final class ExtensionSet $this->staging = new StagingExtension(); } - public function initRuntime() + public function initRuntime(): void { $this->runtimeInitialized = true; } @@ -117,19 +129,28 @@ final class ExtensionSet return $this->lastModified; } + $lastModified = 0; foreach ($this->extensions as $extension) { - $r = new \ReflectionObject($extension); - if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) { - $this->lastModified = $extensionTime; + if ($extension instanceof LastModifiedExtensionInterface) { + $lastModified = max($extension->getLastModified(), $lastModified); + } else { + $r = new \ReflectionObject($extension); + if (is_file($r->getFileName())) { + $lastModified = max(filemtime($r->getFileName()), $lastModified); + } } } - return $this->lastModified; + return $this->lastModified = $lastModified; } public function addExtension(ExtensionInterface $extension): void { - $class = \get_class($extension); + if ($extension instanceof AttributeExtension) { + $class = $extension->getClass(); + } else { + $class = $extension::class; + } if ($this->initialized) { throw new \LogicException(\sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); @@ -190,6 +211,9 @@ final class ExtensionSet return null; } + /** + * @param callable(string): (TwigFunction|false) $callable + */ public function registerUndefinedFunctionCallback(callable $callable): void { $this->functionCallbacks[] = $callable; @@ -243,6 +267,9 @@ final class ExtensionSet return null; } + /** + * @param callable(string): (TwigFilter|false) $callable + */ public function registerUndefinedFilterCallback(callable $callable): void { $this->filterCallbacks[] = $callable; @@ -309,6 +336,9 @@ final class ExtensionSet return null; } + /** + * @param callable(string): (TokenParserInterface|false) $callable + */ public function registerUndefinedTokenParserCallback(callable $callable): void { $this->parserCallbacks[] = $callable; @@ -383,31 +413,30 @@ final class ExtensionSet } } + foreach ($this->testCallbacks as $callback) { + if (false !== $test = $callback($name)) { + return $test; + } + } + return null; } /** - * @return array}> + * @param callable(string): (TwigTest|false) $callable */ - public function getUnaryOperators(): array + public function registerUndefinedTestCallback(callable $callable): void { - if (!$this->initialized) { - $this->initExtensions(); - } - - return $this->unaryOperators; + $this->testCallbacks[] = $callable; } - /** - * @return array, associativity: ExpressionParser::OPERATOR_*}> - */ - public function getBinaryOperators(): array + public function getExpressionParsers(): ExpressionParsers { if (!$this->initialized) { $this->initExtensions(); } - return $this->binaryOperators; + return $this->expressionParsers; } private function initExtensions(): void @@ -420,8 +449,7 @@ final class ExtensionSet $this->dynamicFunctions = []; $this->dynamicTests = []; $this->visitors = []; - $this->unaryOperators = []; - $this->binaryOperators = []; + $this->expressionParsers = new ExpressionParsers(); foreach ($this->extensions as $extension) { $this->initExtension($extension); @@ -471,18 +499,66 @@ final class ExtensionSet $this->visitors[] = $visitor; } - // operators - if ($operators = $extension->getOperators()) { - if (!\is_array($operators)) { - throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); - } + // expression parsers + if (method_exists($extension, 'getExpressionParsers')) { + $this->expressionParsers->add($extension->getExpressionParsers()); + } - if (2 !== \count($operators)) { - throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); - } + $operators = $extension->getOperators(); + if (!\is_array($operators)) { + throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', $extension::class, get_debug_type($operators).(\is_resource($operators) ? '' : '#'.$operators))); + } - $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); - $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + if (2 !== \count($operators)) { + throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', $extension::class, \count($operators))); + } + + $expressionParsers = []; + foreach ($operators[0] as $operator => $op) { + $expressionParsers[] = new UnaryOperatorExpressionParser($op['class'], $operator, $op['precedence'], $op['precedence_change'] ?? null, '', $op['aliases'] ?? []); + } + foreach ($operators[1] as $operator => $op) { + $op['associativity'] = match ($op['associativity']) { + 1 => InfixAssociativity::Left, + 2 => InfixAssociativity::Right, + default => throw new \InvalidArgumentException(\sprintf('Invalid associativity "%s" for operator "%s".', $op['associativity'], $operator)), + }; + + if (isset($op['callable'])) { + $expressionParsers[] = $this->convertInfixExpressionParser($op['class'], $operator, $op['precedence'], $op['associativity'], $op['precedence_change'] ?? null, $op['aliases'] ?? [], $op['callable']); + } else { + $expressionParsers[] = new BinaryOperatorExpressionParser($op['class'], $operator, $op['precedence'], $op['associativity'], $op['precedence_change'] ?? null, '', $op['aliases'] ?? []); + } + } + + if (\count($expressionParsers)) { + trigger_deprecation('twig/twig', '3.21', \sprintf('Extension "%s" uses the old signature for "getOperators()", please implement "getExpressionParsers()" instead.', $extension::class)); + + $this->expressionParsers->add($expressionParsers); } } + + private function convertInfixExpressionParser(string $nodeClass, string $operator, int $precedence, InfixAssociativity $associativity, ?PrecedenceChange $precedenceChange, array $aliases, callable $callable): InfixExpressionParserInterface + { + trigger_deprecation('twig/twig', '3.21', \sprintf('Using a non-ExpressionParserInterface object to define the "%s" binary operator is deprecated.', $operator)); + + return new class($nodeClass, $operator, $precedence, $associativity, $precedenceChange, $aliases, $callable) extends BinaryOperatorExpressionParser { + public function __construct( + string $nodeClass, + string $operator, + int $precedence, + InfixAssociativity $associativity = InfixAssociativity::Left, + ?PrecedenceChange $precedenceChange = null, + array $aliases = [], + private $callable = null, + ) { + parent::__construct($nodeClass, $operator, $precedence, $associativity, $precedenceChange, $aliases); + } + + public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression + { + return ($this->callable)($parser, $expr); + } + }; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php b/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php index 812071bf9..2785ab7f4 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php +++ b/data/web/inc/lib/vendor/twig/twig/src/FileExtensionEscapingStrategy.php @@ -33,7 +33,7 @@ class FileExtensionEscapingStrategy */ public static function guess(string $name) { - if (\in_array(substr($name, -1), ['/', '\\'])) { + if (\in_array(substr($name, -1), ['/', '\\'], true)) { return 'html'; // return html for directories } @@ -45,6 +45,7 @@ class FileExtensionEscapingStrategy switch ($extension) { case 'js': + case 'json': return 'js'; case 'css': diff --git a/data/web/inc/lib/vendor/twig/twig/src/Lexer.php b/data/web/inc/lib/vendor/twig/twig/src/Lexer.php index 28feaa2c1..25b9f2143 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Lexer.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Lexer.php @@ -13,6 +13,7 @@ namespace Twig; use Twig\Error\SyntaxError; +use Twig\ExpressionParser\ExpressionParsers; /** * @author Fabien Potencier @@ -36,6 +37,10 @@ class Lexer private $position; private $positions; private $currentVarBlockLine; + /** @var list */ + private array $documentation = []; + private array $openingBrackets = ['{', '(', '[']; + private array $closingBrackets = ['}', ')', ']']; public const STATE_DATA = 0; public const STATE_BLOCK = 1; @@ -44,12 +49,23 @@ class Lexer public const STATE_INTERPOLATION = 4; public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; - public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + + public const REGEX_NUMBER = '/(?(DEFINE) + (?[0-9]+(_[0-9]+)*) # Integers (with underscores) 123_456 + (?\.(?&LNUM)) # Fractional part .456 + (?[eE][+-]?(?&LNUM)) # Exponent part E+10 + (?(?&LNUM)(?:(?&FRAC))?) # Decimal number 123_456.456 + )(?:(?&DNUM)(?:(?&EXPONENT))?) # 123_456.456E+10 + /Ax'; + public const REGEX_DQ_STRING_DELIM = '/"/A'; public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + public const REGEX_INLINE_COMMENT = '/#[^\n]*/A'; public const PUNCTUATION = '()[]{}?:.,|'; + private const REGEX_RAW_INLINE_COMMENT = '/#[^\r\n]*/A'; + private const SPECIAL_CHARS = [ 'f' => "\f", 'n' => "\n", @@ -73,12 +89,16 @@ class Lexer ], $options); } - private function initialize() + private function initialize(): void { if ($this->isInitialized) { return; } + // when the comment closing tag starts with "#" (as "#}" does), the empty comment ("{##}") + // starts like a documentation comment ("{##"); make sure it keeps lexing as a comment + $emptyCommentLookahead = str_starts_with($this->options['tag_comment'][1], '#') ? '(?!'.preg_quote(substr($this->options['tag_comment'][1], 1), '#').')' : ''; + // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default $this->regexes = [ // }} @@ -101,7 +121,7 @@ class Lexer '|'. preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* '|'. - preg_quote($this->options['tag_block'][1], '#').'\n?'. // %}\n? + preg_quote($this->options['tag_block'][1], '#').'(?:\r\n?|\n)?'. // %}(?:\r\n?|\n)? ') }Ax', @@ -131,7 +151,22 @@ class Lexer '|'. preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* '|'. - preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n? + preg_quote($this->options['tag_comment'][1], '#').'(?:\r\n?|\n)?'. // #}(?:\r\n?|\n)? + ') + }sx', + + // #} or ##} + 'lex_documentation_comment' => '{ + (?:'. + preg_quote($this->options['whitespace_trim'].'#'.$this->options['tag_comment'][1], '#').'\s*\n?'. // -##}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].'#'.$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~##}[ \t\0\x0B]* + '|'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_comment'][1], '#').'(?:\r\n?|\n)?'. // #}(?:\r\n?|\n)? ') }sx', @@ -156,6 +191,8 @@ class Lexer '|'. preg_quote($this->options['tag_block'][0], '#'). // {% '|'. + preg_quote($this->options['tag_comment'][0].'#', '#').$emptyCommentLookahead. // {## (but not the empty comment {##}) + '|'. preg_quote($this->options['tag_comment'][0], '#'). // {# ')('. preg_quote($this->options['whitespace_trim'], '#'). // - @@ -175,7 +212,7 @@ class Lexer $this->initialize(); $this->source = $source; - $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); + $this->code = $source->getCode(); $this->cursor = 0; $this->lineno = 1; $this->end = \strlen($this->code); @@ -184,6 +221,7 @@ class Lexer $this->states = []; $this->brackets = []; $this->position = -1; + $this->documentation = []; // find all token starts in one go preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE); @@ -217,7 +255,7 @@ class Lexer $this->pushToken(Token::EOF_TYPE); - if (!empty($this->brackets)) { + if ($this->brackets) { [$expect, $lineno] = array_pop($this->brackets); throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } @@ -229,8 +267,9 @@ class Lexer { // if no matches are left we return the rest of the template as simple text token if ($this->position == \count($this->positions[0]) - 1) { - $this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor)); - $this->cursor = $this->end; + $text = substr($this->code, $this->cursor); + $this->pushToken(Token::TEXT_TYPE, $this->normalizeNewlines($text)); + $this->moveCursor($text); return; } @@ -258,32 +297,47 @@ class Lexer $text = rtrim($text, " \t\0\x0B"); } } - $this->pushToken(Token::TEXT_TYPE, $text); - $this->moveCursor($textContent.$position[0]); + $this->pushToken(Token::TEXT_TYPE, $this->normalizeNewlines($text)); + $this->moveCursor($textContent); switch ($this->positions[1][$this->position][0]) { + case $this->options['tag_comment'][0].'#': + $this->moveCursor($position[0]); + $this->lexComment(true); + break; + case $this->options['tag_comment'][0]: + $this->moveCursor($position[0]); $this->lexComment(); break; case $this->options['tag_block'][0]: + $lineno = $this->lineno; + $cursor = $this->cursor; + $this->moveCursor($position[0]); + // raw data? if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { + $this->documentation = []; $this->moveCursor($match[0]); $this->lexRawData(); // {% line \d+ %} } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { + $this->documentation = []; $this->moveCursor($match[0]); $this->lineno = (int) $match[1]; } else { - $this->pushToken(Token::BLOCK_START_TYPE); + $this->pushToken(Token::BLOCK_START_TYPE, '', $cursor, $lineno); $this->pushState(self::STATE_BLOCK); $this->currentVarBlockLine = $this->lineno; } break; case $this->options['tag_variable'][0]: - $this->pushToken(Token::VAR_START_TYPE); + $lineno = $this->lineno; + $cursor = $this->cursor; + $this->moveCursor($position[0]); + $this->pushToken(Token::VAR_START_TYPE, '', $cursor, $lineno); $this->pushState(self::STATE_VAR); $this->currentVarBlockLine = $this->lineno; break; @@ -292,9 +346,8 @@ class Lexer private function lexBlock(): void { - if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::BLOCK_END_TYPE); - $this->moveCursor($match[0]); + if (!$this->brackets && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { + $this->pushClosingToken(Token::BLOCK_END_TYPE, $match[0]); $this->popState(); } else { $this->lexExpression(); @@ -303,9 +356,8 @@ class Lexer private function lexVar(): void { - if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::VAR_END_TYPE); - $this->moveCursor($match[0]); + if (!$this->brackets && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { + $this->pushClosingToken(Token::VAR_END_TYPE, $match[0]); $this->popState(); } else { $this->lexExpression(); @@ -323,19 +375,13 @@ class Lexer } } - // spread operator - if ('.' === $this->code[$this->cursor] && ($this->cursor + 2 < $this->end) && '.' === $this->code[$this->cursor + 1] && '.' === $this->code[$this->cursor + 2]) { - $this->pushToken(Token::SPREAD_TYPE, '...'); - $this->moveCursor('...'); - } - // arrow function - elseif ('=' === $this->code[$this->cursor] && ($this->cursor + 1 < $this->end) && '>' === $this->code[$this->cursor + 1]) { - $this->pushToken(Token::ARROW_TYPE, '=>'); - $this->moveCursor('=>'); - } // operators - elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); + if (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { + $operator = preg_replace('/\s+/', ' ', $match[0]); + if (\in_array($operator, $this->openingBrackets, true)) { + $this->checkBrackets($operator); + } + $this->pushToken(Token::OPERATOR_TYPE, $operator); $this->moveCursor($match[0]); } // names @@ -345,37 +391,18 @@ class Lexer } // numbers elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { - $number = (float) $match[0]; // floats - if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) { - $number = (int) $match[0]; // integers lower than the maximum - } - $this->pushToken(Token::NUMBER_TYPE, $number); + $this->pushToken(Token::NUMBER_TYPE, 0 + str_replace('_', '', $match[0])); $this->moveCursor($match[0]); } // punctuation elseif (str_contains(self::PUNCTUATION, $this->code[$this->cursor])) { - // opening bracket - if (str_contains('([{', $this->code[$this->cursor])) { - $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; - } - // closing bracket - elseif (str_contains(')]}', $this->code[$this->cursor])) { - if (empty($this->brackets)) { - throw new SyntaxError(\sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); - } - - [$expect, $lineno] = array_pop($this->brackets); - if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { - throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); - } - } - + $this->checkBrackets($this->code[$this->cursor]); $this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); - ++$this->cursor; + $this->moveCursor($this->code[$this->cursor]); } // strings elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::STRING_TYPE, $this->stripcslashes(substr($match[0], 1, -1), substr($match[0], 0, 1))); + $this->pushToken(Token::STRING_TYPE, $this->stripcslashes($this->normalizeNewlines(substr($match[0], 1, -1)), substr($match[0], 0, 1))); $this->moveCursor($match[0]); } // opening double quoted string @@ -384,9 +411,16 @@ class Lexer $this->pushState(self::STATE_STRING); $this->moveCursor($match[0]); } + // inline comment + elseif (preg_match(self::REGEX_RAW_INLINE_COMMENT, $this->code, $match, 0, $this->cursor)) { + if (str_starts_with($match[0], '##')) { + $this->addDocumentation(substr($match[0], 2)); + } + $this->moveCursor($match[0]); + } // unlexable else { - throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source, columnno: $this->source->getColumn($this->cursor)); } } @@ -418,7 +452,7 @@ class Lexer $result .= $nextChar; } elseif ("'" === $nextChar || '"' === $nextChar) { if ($nextChar !== $quoteType) { - trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1); + trigger_deprecation('twig/twig', '3.12', 'Character "%s" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position %d in "%s" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno); } $result .= $nextChar; } elseif ('#' === $nextChar && $i + 1 < $length && '{' === $str[$i + 1]) { @@ -435,9 +469,9 @@ class Lexer while ($i + 1 < $length && ctype_digit($str[$i + 1]) && $str[$i + 1] < '8' && \strlen($octal) < 3) { $octal .= $str[++$i]; } - $result .= \chr(octdec($octal)); + $result .= \chr(octdec($octal) % 256); } else { - trigger_deprecation('twig/twig', '3.12', 'Character "%s" at position %d should not be escaped; the "\" character is ignored in Twig v3 but will not be in v4. Please remove the extra "\" character.', $nextChar, $i + 1); + trigger_deprecation('twig/twig', '3.12', 'Character "%s" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position %d in "%s" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno); $result .= $nextChar; } @@ -453,6 +487,7 @@ class Lexer throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source); } + $offset = $this->cursor; $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); $this->moveCursor($text.$match[0][0]); @@ -468,16 +503,27 @@ class Lexer } } - $this->pushToken(Token::TEXT_TYPE, $text); + $this->pushToken(Token::TEXT_TYPE, $this->normalizeNewlines($text), $offset); } - private function lexComment(): void + private function lexComment(bool $isDocumentation = false): void { - if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { + $regex = $isDocumentation ? 'lex_documentation_comment' : 'lex_comment'; + if (!preg_match($this->regexes[$regex], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); } - $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); + $comment = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); + if ($isDocumentation) { + $documentation = $comment; + // support a symmetric "##}" closing marker + if (str_ends_with($documentation, '#')) { + $documentation = substr($documentation, 0, -1); + } + $this->addDocumentation($documentation); + } + + $this->moveCursor($comment.$match[0][0]); } private function lexString(): void @@ -488,7 +534,7 @@ class Lexer $this->moveCursor($match[0]); $this->pushState(self::STATE_INTERPOLATION); } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $match[0]) { - $this->pushToken(Token::STRING_TYPE, $this->stripcslashes($match[0], '"')); + $this->pushToken(Token::STRING_TYPE, $this->stripcslashes($this->normalizeNewlines($match[0]), '"')); $this->moveCursor($match[0]); } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { [$expect, $lineno] = array_pop($this->brackets); @@ -497,10 +543,10 @@ class Lexer } $this->popState(); - ++$this->cursor; + $this->moveCursor($match[0]); } else { // unlexable - throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source, columnno: $this->source->getColumn($this->cursor)); } } @@ -509,53 +555,84 @@ class Lexer $bracket = end($this->brackets); if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { array_pop($this->brackets); - $this->pushToken(Token::INTERPOLATION_END_TYPE); - $this->moveCursor($match[0]); + $this->pushClosingToken(Token::INTERPOLATION_END_TYPE, $match[0]); $this->popState(); } else { $this->lexExpression(); } } - private function pushToken($type, $value = ''): void + private function pushToken($type, $value = '', ?int $offset = null, ?int $lineno = null): void { // do not push empty text tokens if (Token::TEXT_TYPE === $type && '' === $value) { return; } - $this->tokens[] = new Token($type, $value, $this->lineno); + // by default the token starts at the current cursor; callers that + // emit a token after consuming it must pass an explicit offset + $documentation = null; + if ($this->documentation && (Token::TEXT_TYPE !== $type || '' !== trim($value))) { + $documentation = implode("\n", $this->documentation); + $this->documentation = []; + } + + $this->tokens[] = new Token($type, $value, $lineno ?? $this->lineno, $offset ?? $this->cursor, $documentation); + } + + private function addDocumentation(string $documentation): void + { + if ('' !== $documentation = trim($this->normalizeNewlines($documentation))) { + $this->documentation[] = $documentation; + } } private function moveCursor($text): void { $this->cursor += \strlen($text); - $this->lineno += substr_count($text, "\n"); + // count "\r\n" and "\r" as a single newline without allocating a + // normalized copy when the chunk has no carriage return (common case) + $this->lineno += str_contains($text, "\r") ? substr_count($this->normalizeNewlines($text), "\n") : substr_count($text, "\n"); + } + + private function normalizeNewlines(string $text): string + { + return str_replace(["\r\n", "\r"], "\n", $text); + } + + private function pushClosingToken(int $type, string $match): void + { + $leadingWhitespaceLength = \strlen($match) - \strlen(ltrim($match)); + if ($leadingWhitespaceLength) { + $this->moveCursor(substr($match, 0, $leadingWhitespaceLength)); + } + + $this->pushToken($type); + $this->moveCursor(substr($match, $leadingWhitespaceLength)); } private function getOperatorRegex(): string { - $operators = array_merge( - ['='], - array_keys($this->env->getUnaryOperators()), - array_keys($this->env->getBinaryOperators()) - ); + $expressionParsers = []; + foreach ($this->env->getExpressionParsers() as $expressionParser) { + $expressionParsers = array_merge($expressionParsers, ExpressionParsers::getOperatorTokensFor($expressionParser)); + } - $operators = array_combine($operators, array_map('strlen', $operators)); - arsort($operators); + $expressionParsers = array_combine($expressionParsers, array_map('strlen', $expressionParsers)); + arsort($expressionParsers); $regex = []; - foreach ($operators as $operator => $length) { + foreach ($expressionParsers as $expressionParser => $length) { // an operator that ends with a character must be followed by // a whitespace, a parenthesis, an opening map [ or sequence { - $r = preg_quote($operator, '/'); - if (ctype_alpha($operator[$length - 1])) { + $r = preg_quote($expressionParser, '/'); + if (ctype_alpha($expressionParser[$length - 1])) { $r .= '(?=[\s()\[{])'; } // an operator that begins with a character must not have a dot or pipe before - if (ctype_alpha($operator[0])) { - $r = '(?state = array_pop($this->states); } + + private function checkBrackets(string $code): void + { + // opening bracket + if (\in_array($code, $this->openingBrackets, true)) { + $this->brackets[] = [$code, $this->lineno]; + } elseif (\in_array($code, $this->closingBrackets, true)) { + // closing bracket + if (!$this->brackets) { + throw new SyntaxError(\sprintf('Unexpected "%s".', $code), $this->lineno, $this->source, columnno: $this->source->getColumn($this->cursor)); + } + + [$expect, $lineno] = array_pop($this->brackets); + if ($code !== str_replace($this->openingBrackets, $this->closingBrackets, $expect)) { + throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + } + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php b/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php index 6e4f9511c..0859dcd2f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Loader/ChainLoader.php @@ -104,7 +104,7 @@ final class ChainLoader implements LoaderInterface try { return $loader->getCacheKey($name); } catch (LoaderError $e) { - $exceptions[] = \get_class($loader).': '.$e->getMessage(); + $exceptions[] = $loader::class.': '.$e->getMessage(); } } @@ -123,7 +123,7 @@ final class ChainLoader implements LoaderInterface try { return $loader->isFresh($name, $time); } catch (LoaderError $e) { - $exceptions[] = \get_class($loader).': '.$e->getMessage(); + $exceptions[] = $loader::class.': '.$e->getMessage(); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php b/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php index c60964f5f..49f2b8915 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Loader/FilesystemLoader.php @@ -24,6 +24,9 @@ class FilesystemLoader implements LoaderInterface /** Identifier of the main namespace. */ public const MAIN_NAMESPACE = '__main__'; + /** + * @var array> + */ protected $paths = []; protected $cache = []; protected $errorCache = []; @@ -31,8 +34,8 @@ class FilesystemLoader implements LoaderInterface private $rootPath; /** - * @param string|array $paths A path or an array of paths where to look for templates - * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) + * @param string|string[] $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) */ public function __construct($paths = [], ?string $rootPath = null) { @@ -48,6 +51,8 @@ class FilesystemLoader implements LoaderInterface /** * Returns the paths to the templates. + * + * @return list */ public function getPaths(string $namespace = self::MAIN_NAMESPACE): array { @@ -58,6 +63,8 @@ class FilesystemLoader implements LoaderInterface * Returns the path namespaces. * * The main namespace is always defined. + * + * @return list */ public function getNamespaces(): array { @@ -65,7 +72,7 @@ class FilesystemLoader implements LoaderInterface } /** - * @param string|array $paths A path or an array of paths where to look for templates + * @param string|string[] $paths A path or an array of paths where to look for templates */ public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void { diff --git a/data/web/inc/lib/vendor/twig/twig/src/MacroNamespace.php b/data/web/inc/lib/vendor/twig/twig/src/MacroNamespace.php new file mode 100644 index 000000000..13ca706bc --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/MacroNamespace.php @@ -0,0 +1,136 @@ + $macros + */ + public function __construct( + private Template $template, + private array $macros, + ) { + } + + public function has(string $name, array $context): bool + { + if (null !== $declaration = $this->findDeclaredName($name, $context)) { + [$declaredName, $templateName] = $declaration; + if ($declaredName !== $name) { + trigger_deprecation('twig/twig', '3.29', 'Testing whether the macro "%s" (defined in template "%s") is defined as "%s" is deprecated; macro names will be case-sensitive in Twig 4.0 and this test will return false.', $declaredName, $templateName, $name); + } + + return true; + } + + if (!str_starts_with($name, 'macro_') || null === $this->findDeclaredName($bareName = substr($name, \strlen('macro_')), $context)) { + return false; + } + + trigger_deprecation('twig/twig', '3.29', 'Testing whether the macro "%s" is defined via the "macro_"-prefixed name "%s" is deprecated; pass the bare macro name to "%s" instead.', $bareName, $name, MacroReferenceExpression::class); + + return true; + } + + /** + * @param array $arguments + */ + public function call(string $name, array $arguments, array $context, int $line, Source $source): string|Markup + { + if (null !== $macro = $this->getDeclared($name)) { + return $macro->callLegacy($arguments, $source, $line); + } + + if (null === $macro = $this->resolve($name, $context)) { + if (!str_starts_with($name, 'macro_') || null === $macro = $this->resolve($bareName = substr($name, \strlen('macro_')), $context)) { + throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', $name, $this->template->getTemplateName()), $line, $source); + } + + trigger_deprecation('twig/twig', '3.29', 'Calling the macro "%s" via the "macro_"-prefixed name "%s" is deprecated; pass the bare macro name to "%s" instead.', $bareName, $name, MacroReferenceExpression::class); + } + + return $macro->callLegacy($arguments, $source, $line); + } + + /** + * @return array{string, string}|null + */ + private function findDeclaredName(string $name, array $context): ?array + { + $namespace = $this; + while (true) { + if (isset($namespace->macros[$name])) { + return [$name, $namespace->template->getTemplateName()]; + } + foreach ($namespace->macros as $declaredName => $macro) { + if (0 === strcasecmp($declaredName, $name)) { + return [$declaredName, $namespace->template->getTemplateName()]; + } + } + + if (null === $namespace = $namespace->getParent($context)) { + return null; + } + } + } + + private function getDeclared(string $name): ?TwigMacro + { + if (isset($this->macros[$name])) { + $this->template->ensureSecurityChecked(); + + return $this->macros[$name]; + } + + foreach ($this->macros as $declaredName => $macro) { + if (0 === strcasecmp($declaredName, $name)) { + trigger_deprecation('twig/twig', '3.29', 'Calling the macro "%s" (defined in template "%s") as "%s" is deprecated; macro names will be case-sensitive in Twig 4.0.', $declaredName, $this->template->getTemplateName(), $name); + + $this->template->ensureSecurityChecked(); + + return $macro; + } + } + + return null; + } + + private function resolve(string $name, array $context): ?TwigMacro + { + $namespace = $this; + while (true) { + if (null !== $macro = $namespace->getDeclared($name)) { + return $macro; + } + + if (null === $namespace = $namespace->getParent($context)) { + return null; + } + } + } + + private function getParent(array $context): ?self + { + if (!$parent = $this->template->getParent($context)) { + return null; + } + + return $parent->unwrap()->getMacroNamespace(); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Markup.php b/data/web/inc/lib/vendor/twig/twig/src/Markup.php index 4fae779ee..820f09143 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Markup.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Markup.php @@ -14,6 +14,16 @@ namespace Twig; /** * Marks a content as safe. * + * Instances of this class (and existing subclasses) are trusted by the Twig + * sandbox: method calls and property accesses on a Markup instance bypass the + * SecurityPolicy method/property allowlists. This is by design: Markup + * represents content that has already been deemed safe to output. + * + * This class is considered final as of Twig 3.28 and will be final in Twig + * 4.0. + * + * @final since Twig 3.28 + * * @author Fabien Potencier */ class Markup implements \Countable, \JsonSerializable, \Stringable @@ -27,11 +37,16 @@ class Markup implements \Countable, \JsonSerializable, \Stringable $this->charset = $charset; } - public function __toString() + public function __toString(): string { return $this->content; } + public function getCharset(): string + { + return $this->charset; + } + /** * @return int */ diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php index b4f939cf6..b736b2d4c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/BlockNode.php @@ -42,7 +42,7 @@ class BlockNode extends Node $compiler ->subcompile($this->getNode('body')) - ->write("yield from [];\n") + ->write("return; yield;\n") ->outdent() ->write("}\n\n") ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php index 3b7f0b6d8..2f4ef5a79 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CaptureNode.php @@ -39,7 +39,7 @@ class CaptureNode extends Node ->raw("(function () use (&\$context, \$macros, \$blocks) {\n") ->indent() ->subcompile($this->getNode('body')) - ->write("yield from [];\n") + ->write("return; yield;\n") ->outdent() ->write('})()') ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php index 9c162d129..b5337aee8 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityCallNode.php @@ -15,16 +15,21 @@ use Twig\Attribute\YieldReady; use Twig\Compiler; /** + * Wires the security checker at the very top of the template constructor, as + * the constructor resolves `use` traits before the sandbox could check them. + * * @author Fabien Potencier */ #[YieldReady] class CheckSecurityCallNode extends Node { + /** + * @return void + */ public function compile(Compiler $compiler) { $compiler - ->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n") - ->write("\$this->checkSecurity();\n") + ->write("\$this->sandbox = \$env->getExtension(SandboxExtension::class)->getChecker();\n") ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php index 6e591aad4..0a7ed8ff3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckSecurityNode.php @@ -23,45 +23,90 @@ class CheckSecurityNode extends Node private $usedFilters; private $usedTags; private $usedFunctions; + private $usedTests; /** * @param array $usedFilters * @param array $usedTags * @param array $usedFunctions + * @param array $usedTests */ - public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions, array $usedTests = []) { + if (\func_num_args() < 4) { + trigger_deprecation('twig/twig', '3.28', 'Not passing the "$usedTests" argument to "%s::__construct()" is deprecated; it will be required in 4.0.', static::class); + } + $this->usedFilters = $usedFilters; $this->usedTags = $usedTags; $this->usedFunctions = $usedFunctions; + $this->usedTests = $usedTests; parent::__construct(); } public function compile(Compiler $compiler): void { + if (isset($this->usedTags['use'])) { + $compiler + ->write("\n") + ->write("protected function checkTraitsAllowed(): void\n") + ->write("{\n") + ->indent() + ->write("if (!\$this->sandbox->isSandboxed(\$this->source)) {\n") + ->indent() + ->write("return;\n") + ->outdent() + ->write("}\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->sandbox->checkSecurity(['use'], [], [], [], \$this->source);\n") + ->outdent() + ->write("} catch (SecurityNotAllowedTagError \$e) {\n") + ->indent() + ->write('$e->setTemplateLine(')->repr($this->usedTags['use'])->raw(");\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ; + } + $compiler + ->write("\n") + ->write("public function ensureSecurityChecked(): void\n") + ->write("{\n") + ->indent() + ->write("if (\$this->sandbox->isSandboxed(\$this->source)) {\n") + ->indent() + ->write("\$this->checkSecurity();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") ->write("\n") ->write("public function checkSecurity()\n") ->write("{\n") ->indent() ->write('static $tags = ')->repr(array_filter($this->usedTags))->raw(";\n") ->write('static $filters = ')->repr(array_filter($this->usedFilters))->raw(";\n") - ->write('static $functions = ')->repr(array_filter($this->usedFunctions))->raw(";\n\n") + ->write('static $functions = ')->repr(array_filter($this->usedFunctions))->raw(";\n") + ->write('static $tests = ')->repr(array_filter($this->usedTests))->raw(";\n\n") ->write("try {\n") ->indent() ->write("\$this->sandbox->checkSecurity(\n") ->indent() - ->write(!$this->usedTags ? "[],\n" : "['".implode("', '", array_keys($this->usedTags))."'],\n") - ->write(!$this->usedFilters ? "[],\n" : "['".implode("', '", array_keys($this->usedFilters))."'],\n") - ->write(!$this->usedFunctions ? "[],\n" : "['".implode("', '", array_keys($this->usedFunctions))."'],\n") + ->write('')->repr(array_keys($this->usedTags))->raw(",\n") + ->write('')->repr(array_keys($this->usedFilters))->raw(",\n") + ->write('')->repr(array_keys($this->usedFunctions))->raw(",\n") + ->write('')->repr(array_keys($this->usedTests))->raw(",\n") ->write("\$this->source\n") ->outdent() ->write(");\n") ->outdent() ->write("} catch (SecurityError \$e) {\n") ->indent() - ->write("\$e->setSourceContext(\$this->source);\n\n") ->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") ->indent() ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") @@ -74,6 +119,10 @@ class CheckSecurityNode extends Node ->indent() ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedTestError && isset(\$tests[\$e->getTestName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tests[\$e->getTestName()]);\n") + ->outdent() ->write("}\n\n") ->write("throw \$e;\n") ->outdent() diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php index 937240c1d..11aec9cc0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CheckToStringNode.php @@ -28,16 +28,17 @@ use Twig\Node\Expression\AbstractExpression; #[YieldReady] class CheckToStringNode extends AbstractExpression { - public function __construct(AbstractExpression $expr) + public function __construct(AbstractExpression $expr, bool $spread = false) { - parent::__construct(['expr' => $expr], [], $expr->getTemplateLine()); + parent::__construct(['expr' => $expr], ['spread' => $spread], $expr->getTemplateLine()); } public function compile(Compiler $compiler): void { $expr = $this->getNode('expr'); + $method = $this->getAttribute('spread') ? 'ensureSpreadAllowed' : 'ensureToStringAllowed'; $compiler - ->raw('$this->sandbox->ensureToStringAllowed(') + ->raw('$this->sandbox->'.$method.'(') ->subcompile($expr) ->raw(', ') ->repr($expr->getTemplateLine()) diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/CoercesChildrenToStringInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Node/CoercesChildrenToStringInterface.php new file mode 100644 index 000000000..e213d1b61 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/CoercesChildrenToStringInterface.php @@ -0,0 +1,40 @@ + + */ +interface CoercesChildrenToStringInterface +{ + /** + * Returns the names of the child nodes that will be coerced to + * string when this node is evaluated. + * + * @return string[] + */ + public function getStringCoercedChildNames(): array; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ConfigNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ConfigNode.php new file mode 100644 index 000000000..da3a978fb --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ConfigNode.php @@ -0,0 +1,30 @@ + + */ +#[YieldReady] +class ConfigNode extends Node +{ + public function __construct(int $lineno) + { + parent::__construct([], [], $lineno); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php index 0772adfc3..fea8d355b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/DeprecatedNode.php @@ -22,7 +22,7 @@ use Twig\Node\Expression\ConstantExpression; * @author Yonel Ceruto */ #[YieldReady] -class DeprecatedNode extends Node +class DeprecatedNode extends Node implements CoercesChildrenToStringInterface { public function __construct(AbstractExpression $expr, int $lineno) { @@ -70,4 +70,18 @@ class DeprecatedNode extends Node ->raw(");\n") ; } + + public function getStringCoercedChildNames(): array + { + // the message is concatenated with `.`, and `package` / `version` are typed `string` on trigger_deprecation() + $names = ['expr']; + if ($this->hasNode('package')) { + $names[] = 'package'; + } + if ($this->hasNode('version')) { + $names[] = 'version'; + } + + return $names; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php index 4cd3b38f2..2de39ebb9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/EmbedNode.php @@ -33,18 +33,22 @@ class EmbedNode extends IncludeNode $this->setAttribute('index', $index); } - protected function addGetTemplate(Compiler $compiler): void + protected function addGetTemplate(Compiler $compiler, string $template = ''): void { $compiler - ->write('$this->loadTemplate(') + ->raw('$this->load(') ->string($this->getAttribute('name')) ->raw(', ') - ->repr($this->getTemplateName()) - ->raw(', ') ->repr($this->getTemplateLine()) ->raw(', ') - ->string($this->getAttribute('index')) + ->repr($this->getAttribute('index')) ->raw(')') ; + if ($this->getAttribute('ignore_missing')) { + $compiler + ->raw(";\n") + ->write(\sprintf("\$%s->getParent(\$context);\n", $template)) + ; + } } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/EmptyNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/EmptyNode.php new file mode 100644 index 000000000..fd4717ff4 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/EmptyNode.php @@ -0,0 +1,33 @@ + + */ +#[YieldReady] +final class EmptyNode extends Node +{ + public function __construct(int $lineno = 0) + { + parent::__construct([], [], $lineno); + } + + public function setNode(string $name, Node $node): void + { + throw new \LogicException('EmptyNode cannot have children.'); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php index 1692f5671..22d8617cd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AbstractExpression.php @@ -25,4 +25,19 @@ abstract class AbstractExpression extends Node { return $this->hasAttribute('is_generator') && $this->getAttribute('is_generator'); } + + /** + * @return static + */ + public function setExplicitParentheses(): self + { + $this->setAttribute('with_parentheses', true); + + return $this; + } + + public function hasExplicitParentheses(): bool + { + return $this->hasAttribute('with_parentheses') && $this->getAttribute('with_parentheses'); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php index 5f8b0f63f..189e02f7e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrayExpression.php @@ -12,9 +12,15 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Error\SyntaxError; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\Unary\SpreadUnary; +use Twig\Node\Expression\Unary\StringCastUnary; -class ArrayExpression extends AbstractExpression +class ArrayExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnArrayInterface, CoercesChildrenToStringInterface { + use SupportDefinedTestTrait; + private $index; public function __construct(array $elements, int $lineno) @@ -55,6 +61,31 @@ class ArrayExpression extends AbstractExpression return false; } + /** + * Checks if the array is a sequence (keys are sequential integers starting from 0). + * + * @internal + */ + public function isSequence(): bool + { + foreach ($this->getKeyValuePairs() as $i => $pair) { + $key = $pair['key']; + if ($key instanceof TempNameExpression) { + $keyValue = $key->getAttribute('name'); + } elseif ($key instanceof ConstantExpression) { + $keyValue = $key->getAttribute('value'); + } else { + return false; + } + + if ($keyValue !== $i) { + return false; + } + } + + return true; + } + public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void { if (null === $key) { @@ -64,72 +95,71 @@ class ArrayExpression extends AbstractExpression array_push($this->nodes, $key, $value); } - public function compile(Compiler $compiler): void + public function getStringCoercedChildNames(): array { - $keyValuePairs = $this->getKeyValuePairs(); - $needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs); - - if ($needsArrayMergeSpread) { - $compiler->raw('CoreExtension::merge('); - } - $compiler->raw('['); - $first = true; - $reopenAfterMergeSpread = false; - $nextIndex = 0; - foreach ($keyValuePairs as $pair) { - if ($reopenAfterMergeSpread) { - $compiler->raw(', ['); - $reopenAfterMergeSpread = false; - } - - if ($needsArrayMergeSpread && $pair['value']->hasAttribute('spread')) { - $compiler->raw('], ')->subcompile($pair['value']); - $first = true; - $reopenAfterMergeSpread = true; + // dynamic mapping keys (computed at runtime) are coerced to string; + // static keys (constants or sequence indexes) are emitted as PHP + // literals by compile() and never trigger a __toString() call + $names = []; + foreach (array_chunk($this->nodes, 2) as $i => $pair) { + $key = $pair[0]; + if ($key instanceof ConstantExpression || $key instanceof TempNameExpression) { continue; } - if (!$first) { + + $names[] = (string) ($i * 2); + } + + return $names; + } + + public function compile(Compiler $compiler): void + { + if ($this->definedTest) { + $compiler->repr(true); + + return; + } + + // Check for empty expressions which are only allowed in destructuring + foreach ($this->getKeyValuePairs() as $pair) { + if ($pair['value'] instanceof EmptyExpression) { + throw new SyntaxError('Empty array elements are only allowed in destructuring assignments.', $pair['value']->getTemplateLine(), $this->getSourceContext()); + } + } + + $compiler->raw('['); + $isSequence = true; + foreach ($this->getKeyValuePairs() as $i => $pair) { + if (0 !== $i) { $compiler->raw(', '); } - $first = false; - if ($pair['value']->hasAttribute('spread') && !$needsArrayMergeSpread) { - $compiler->raw('...')->subcompile($pair['value']); - ++$nextIndex; + $key = null; + if ($pair['key'] instanceof TempNameExpression) { + $key = $pair['key']->getAttribute('name'); + $pair['key'] = new ConstantExpression($key, $pair['key']->getTemplateLine()); + } elseif ($pair['key'] instanceof ConstantExpression) { + $key = $pair['key']->getAttribute('value'); } else { - $key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null; - - if ($nextIndex !== $key) { - if (\is_int($key)) { - $nextIndex = $key + 1; - } - $compiler - ->subcompile($pair['key']) - ->raw(' => ') - ; - } else { - ++$nextIndex; - } - - $compiler->subcompile($pair['value']); + // dynamic key: cast to string so PHP accepts it as an array offset + // (the sandbox visitor has already wrapped it with a __toString policy check) + $pair['key'] = new StringCastUnary($pair['key'], $pair['key']->getTemplateLine()); } - } - if (!$reopenAfterMergeSpread) { - $compiler->raw(']'); - } - if ($needsArrayMergeSpread) { - $compiler->raw(')'); - } - } - private function hasSpreadItem(array $pairs): bool - { - foreach ($pairs as $pair) { - if ($pair['value']->hasAttribute('spread')) { - return true; + if ($key !== $i) { + $isSequence = false; } - } - return false; + if (!$isSequence && !$pair['value'] instanceof SpreadUnary) { + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ; + } + + $compiler->subcompile($pair['value']); + } + $compiler->raw(']'); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php index 2bae4edd7..9156f7c61 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php @@ -12,6 +12,9 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Error\SyntaxError; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; /** @@ -23,6 +26,14 @@ class ArrowFunctionExpression extends AbstractExpression { public function __construct(AbstractExpression $expr, Node $names, $lineno) { + if ($names instanceof ContextVariable) { + $names = new ListExpression([new AssignContextVariable($names->getAttribute('name'), $names->getTemplateLine())], $lineno); + } + + if (!$names instanceof ListExpression) { + throw new SyntaxError('The arrow function argument must be a list of variables or a single variable.', $names->getTemplateLine(), $names->getSourceContext()); + } + parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno); } @@ -31,19 +42,7 @@ class ArrowFunctionExpression extends AbstractExpression $compiler ->addDebugInfo($this) ->raw('function (') - ; - foreach ($this->getNode('names') as $i => $name) { - if ($i) { - $compiler->raw(', '); - } - - $compiler - ->raw('$__') - ->raw($name->getAttribute('name')) - ->raw('__') - ; - } - $compiler + ->subcompile($this->getNode('names')) ->raw(') use ($context, $macros) { ') ; foreach ($this->getNode('names') as $name) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php index 7dd1bc4a3..9a7f0f92b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php @@ -13,9 +13,26 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Error\SyntaxError; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\ContextVariable; -class AssignNameExpression extends NameExpression +class AssignNameExpression extends ContextVariable { + public function __construct(string $name, int $lineno) + { + if (self::class === static::class) { + trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', self::class, AssignContextVariable::class); + } + + // All names supported by ExpressionParser::parsePrimaryExpression() should be excluded + if (\in_array(strtolower($name), ['true', 'false', 'none', 'null'], true)) { + throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $name), $lineno); + } + + parent::__construct($name, $lineno); + } + public function compile(Compiler $compiler): void { $compiler diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php index c424e5cc5..b4bf6662e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php @@ -16,10 +16,21 @@ use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Node; -abstract class AbstractBinary extends AbstractExpression +abstract class AbstractBinary extends AbstractExpression implements BinaryInterface { + /** + * @param AbstractExpression $left + * @param AbstractExpression $right + */ public function __construct(Node $left, Node $right, int $lineno) { + if (!$left instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "left" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $left::class); + } + if (!$right instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "right" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $right::class); + } + parent::__construct(['left' => $left, 'right' => $right], [], $lineno); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php index ee4307e33..42377aea0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php @@ -13,8 +13,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class AddBinary extends AbstractBinary +class AddBinary extends AbstractBinary implements ReturnNumberInterface { public function operator(Compiler $compiler): Compiler { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php index 5f2380da5..7ccef174b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php @@ -13,9 +13,17 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; +use Twig\Node\Expression\Test\TrueTest; +use Twig\Node\Node; -class AndBinary extends AbstractBinary +class AndBinary extends AbstractBinary implements ReturnBoolInterface { + public function __construct(Node $left, Node $right, int $lineno) + { + parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno); + } + public function operator(Compiler $compiler): Compiler { return $compiler->raw('&&'); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/BinaryInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/BinaryInterface.php new file mode 100644 index 000000000..eeeb2eb99 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/BinaryInterface.php @@ -0,0 +1,22 @@ +raw('.'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php index e3817d1cd..11c061e18 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php @@ -13,8 +13,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class DivBinary extends AbstractBinary +class DivBinary extends AbstractBinary implements ReturnNumberInterface { public function operator(Compiler $compiler): Compiler { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ElvisBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ElvisBinary.php new file mode 100644 index 000000000..1340a4f0c --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ElvisBinary.php @@ -0,0 +1,56 @@ +setNode('test', TrueTest::wrap(clone $left)); + $left->setAttribute('always_defined', true); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->subcompile($this->getNode('test')) + ->raw(') ? (') + ->subcompile($this->getNode('left')) + ->raw(') : (') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('?:'); + } + + public function getOperandNamesToEscape(): array + { + return ['left', 'right']; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php index a73a5608d..e689d668a 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -12,8 +12,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; -class EndsWithBinary extends AbstractBinary +class EndsWithBinary extends AbstractBinary implements ReturnBoolInterface { public function compile(Compiler $compiler): void { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php index 5f423196f..d438e2b5d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class EqualBinary extends AbstractBinary +class EqualBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -36,4 +38,9 @@ class EqualBinary extends AbstractBinary { return $compiler->raw('=='); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php index d7e7980ef..a60ab3b21 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php @@ -12,8 +12,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class FloorDivBinary extends AbstractBinary +class FloorDivBinary extends AbstractBinary implements ReturnNumberInterface { public function compile(Compiler $compiler): void { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php index f42de3f86..318c3019d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class GreaterBinary extends AbstractBinary +class GreaterBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -36,4 +38,9 @@ class GreaterBinary extends AbstractBinary { return $compiler->raw('>'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php index 0c4f43fd9..2dacd9f0f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class GreaterEqualBinary extends AbstractBinary +class GreaterEqualBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -36,4 +38,9 @@ class GreaterEqualBinary extends AbstractBinary { return $compiler->raw('>='); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php index c57bb20e9..3ac11cd7e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php @@ -12,8 +12,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; -class HasEveryBinary extends AbstractBinary +class HasEveryBinary extends AbstractBinary implements ReturnBoolInterface { public function compile(Compiler $compiler): void { @@ -22,7 +23,7 @@ class HasEveryBinary extends AbstractBinary ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) - ->raw(')') + ->raw(', $this->env->hasExtension(\Twig\Extension\SandboxExtension::class) && $this->env->getExtension(\Twig\Extension\SandboxExtension::class)->getChecker()->isSandboxed($this->source))') ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php index 12293f84c..6aedd8240 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php @@ -12,8 +12,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; -class HasSomeBinary extends AbstractBinary +class HasSomeBinary extends AbstractBinary implements ReturnBoolInterface { public function compile(Compiler $compiler): void { @@ -22,7 +23,7 @@ class HasSomeBinary extends AbstractBinary ->subcompile($this->getNode('left')) ->raw(', ') ->subcompile($this->getNode('right')) - ->raw(')') + ->raw(', $this->env->hasExtension(\Twig\Extension\SandboxExtension::class) && $this->env->getExtension(\Twig\Extension\SandboxExtension::class)->getChecker()->isSandboxed($this->source))') ; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php index 68a98fe15..2f11a54fd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class InBinary extends AbstractBinary +class InBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -30,4 +32,9 @@ class InBinary extends AbstractBinary { return $compiler->raw('in'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php index fb3264a2d..3bb629122 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class LessBinary extends AbstractBinary +class LessBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -36,4 +38,9 @@ class LessBinary extends AbstractBinary { return $compiler->raw('<'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php index 8f3653892..30217454f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class LessEqualBinary extends AbstractBinary +class LessEqualBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -36,4 +38,9 @@ class LessEqualBinary extends AbstractBinary { return $compiler->raw('<='); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php index 4669044e0..5dd3dbeb0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -12,9 +12,37 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Error\SyntaxError; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\ReturnBoolInterface; +use Twig\Node\Node; -class MatchesBinary extends AbstractBinary +class MatchesBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { + public function __construct(Node $left, Node $right, int $lineno) + { + if (!$left instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.24', 'Passing a "%s" instance to "%s()" first argument is deprecated, pass an "AbstractExpression" instance instead.', $left::class, __METHOD__); + } + if (!$right instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.24', 'Passing a "%s" instance to "%s()" second argument is deprecated, pass an "AbstractExpression" instance instead.', $right::class, __METHOD__); + } + + if ($right instanceof ConstantExpression) { + $regexp = $right->getAttribute('value'); + set_error_handler(static fn ($t, $m) => throw new SyntaxError(\sprintf('Regexp "%s" passed to "matches" is not valid: %s.', $regexp, substr($m, 14)), $lineno)); + try { + preg_match($regexp, ''); + } finally { + restore_error_handler(); + } + } + + parent::__construct($left, $right, $lineno); + } + public function compile(Compiler $compiler): void { $compiler @@ -30,4 +58,9 @@ class MatchesBinary extends AbstractBinary { return $compiler->raw(''); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php index 271b45cac..aef48f3d0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php @@ -13,8 +13,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class ModBinary extends AbstractBinary +class ModBinary extends AbstractBinary implements ReturnNumberInterface { public function operator(Compiler $compiler): Compiler { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php index 6d4c1e0b6..beb881ae3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php @@ -13,8 +13,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class MulBinary extends AbstractBinary +class MulBinary extends AbstractBinary implements ReturnNumberInterface { public function operator(Compiler $compiler): Compiler { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php index d137ef627..c63e32517 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class NotEqualBinary extends AbstractBinary +class NotEqualBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -36,4 +38,9 @@ class NotEqualBinary extends AbstractBinary { return $compiler->raw('!='); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php index 80c8755d8..f46c88ff8 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnBoolInterface; -class NotInBinary extends AbstractBinary +class NotInBinary extends AbstractBinary implements ReturnBoolInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -30,4 +32,9 @@ class NotInBinary extends AbstractBinary { return $compiler->raw('not in'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php new file mode 100644 index 000000000..ed28c19bb --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php @@ -0,0 +1,23 @@ +raw('!=='); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NullCoalesceBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NullCoalesceBinary.php new file mode 100644 index 000000000..ffba62dc6 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/NullCoalesceBinary.php @@ -0,0 +1,71 @@ + true]), new EmptyNode(), $left->getTemplateLine()); + // for "block()", we don't need the null test as the return value is always a string + if (!$left instanceof BlockReferenceExpression) { + $test = new AndBinary( + $test, + new NotUnary(new NullTest($left, new TwigTest('null', null, ['always_allowed_in_sandbox' => true]), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine(), + ); + } + + $left->setAttribute('always_defined', true); + $this->setNode('test', $test); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->subcompile($this->getNode('test')) + ->raw(') ? (') + ->subcompile($this->getNode('left')) + ->raw(') : (') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('??'); + } + + public function getOperandNamesToEscape(): array + { + return ['left', 'right']; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ObjectDestructuringSetBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ObjectDestructuringSetBinary.php new file mode 100644 index 000000000..217266dc8 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/ObjectDestructuringSetBinary.php @@ -0,0 +1,84 @@ + */ + private array $mappings = []; + + /** + * @param ArrayExpression $left The array expression containing object/mapping destructuring properties + * @param AbstractExpression $right The expression providing values for assignment + */ + public function __construct(Node $left, Node $right, int $lineno) + { + if (!$left instanceof ArrayExpression) { + throw new \LogicException('Left side must be ArrayExpression for object/mapping destructuring.'); + } + foreach ($left->getKeyValuePairs() as $pair) { + if (!$pair['value'] instanceof AssignContextVariable) { + throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned in object/mapping destructuring.', $pair['value']::class), $lineno); + } + + $this->mappings[] = [ + 'property' => $pair['key']->getAttribute('value'), + 'variable' => $pair['value']->getAttribute('name'), + ]; + } + + parent::__construct($left, $right, $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + $var = '$'.$compiler->getVarName(); + $compiler->raw('[['); + foreach ($this->mappings as $i => $mapping) { + if ($i) { + $compiler->raw(', '); + } + $compiler->raw('$context[')->repr($mapping['variable'])->raw(']'); + } + $compiler->raw('] = ['); + foreach ($this->mappings as $i => $mapping) { + if ($i) { + $compiler->raw(', '); + } + $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, '); + if (0 === $i) { + $compiler->raw('('.$var.' = ')->subcompile($this->getNode('right'))->raw(')'); + } else { + $compiler->raw($var); + } + $compiler->raw(', ')->repr($mapping['property'])->raw(', [], \\Twig\\Template::ANY_CALL, false, false, ')->repr($compiler->getEnvironment()->hasExtension(SandboxExtension::class))->raw(', ')->repr($this->getNode('right')->getTemplateLine())->raw(')'); + } + $compiler->raw('], '.$var.' = null][0]'); + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('='); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php index 21f87c91b..cacffdaa3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php @@ -13,9 +13,17 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; +use Twig\Node\Expression\Test\TrueTest; +use Twig\Node\Node; -class OrBinary extends AbstractBinary +class OrBinary extends AbstractBinary implements ReturnBoolInterface { + public function __construct(Node $left, Node $right, int $lineno) + { + parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno); + } + public function operator(Compiler $compiler): Compiler { return $compiler->raw('||'); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php index c9f4c6697..5325e8eb0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php @@ -12,8 +12,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class PowerBinary extends AbstractBinary +class PowerBinary extends AbstractBinary implements ReturnNumberInterface { public function operator(Compiler $compiler): Compiler { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php index 55982c819..2d0d2eb5b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php @@ -12,8 +12,10 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnArrayInterface; -class RangeBinary extends AbstractBinary +class RangeBinary extends AbstractBinary implements ReturnArrayInterface, CoercesChildrenToStringInterface { public function compile(Compiler $compiler): void { @@ -30,4 +32,9 @@ class RangeBinary extends AbstractBinary { return $compiler->raw('..'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SameAsBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SameAsBinary.php new file mode 100644 index 000000000..b08a19072 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SameAsBinary.php @@ -0,0 +1,23 @@ +raw('==='); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SequenceDestructuringSetBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SequenceDestructuringSetBinary.php new file mode 100644 index 000000000..b3f8179f9 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SequenceDestructuringSetBinary.php @@ -0,0 +1,78 @@ +getKeyValuePairs() as $pair) { + if ($pair['value'] instanceof EmptyExpression) { + $this->variables[] = null; + } elseif ($pair['value'] instanceof AssignContextVariable) { + $this->variables[] = $pair['value']->getAttribute('name'); + } else { + throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned in sequence destructuring.', $pair['value']::class), $lineno); + } + } + + parent::__construct($left, $right, $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + $var = '$'.$compiler->getVarName(); + $compiler + ->raw('[(('.$var.' = ') + ->subcompile($this->getNode('right')) + ->raw(') instanceof \Traversable ? CoreExtension::destructureSequence($context, ') + ->repr($this->variables) + ->raw(', '.$var.') : ([') + ; + foreach ($this->variables as $i => $name) { + if ($i) { + $compiler->raw(', '); + } + if (null !== $name) { + $compiler->raw('$context[')->repr($name)->raw(']'); + } + } + $compiler + ->raw('] = array_pad('.$var.', ') + ->repr(\count($this->variables)) + ->raw(', null))), '.$var.' = null][0]') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('='); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SetBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SetBinary.php new file mode 100644 index 000000000..7b3443298 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SetBinary.php @@ -0,0 +1,44 @@ + + */ +class SetBinary extends AbstractBinary +{ + /** + * @param ContextVariable $left + * @param AbstractExpression $right + */ + public function __construct(Node $left, Node $right, int $lineno) + { + $name = $left->getAttribute('name'); + if (!\is_string($name)) { + throw new \LogicException('The "name" attribute must be a string.'); + } + $left = new AssignContextVariable($name, $left->getTemplateLine()); + + parent::__construct($left, $right, $lineno); + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('='); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php index ae5a4a493..2fb5ddf9e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php @@ -12,11 +12,18 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ReturnNumberInterface; -class SpaceshipBinary extends AbstractBinary +class SpaceshipBinary extends AbstractBinary implements ReturnNumberInterface, CoercesChildrenToStringInterface { public function operator(Compiler $compiler): Compiler { return $compiler->raw('<=>'); } + + public function getStringCoercedChildNames(): array + { + return ['left', 'right']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php index 4519f30d9..ef2fc9502 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -12,8 +12,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; -class StartsWithBinary extends AbstractBinary +class StartsWithBinary extends AbstractBinary implements ReturnBoolInterface { public function compile(Compiler $compiler): void { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php index eeb87faca..10663f5c1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php @@ -13,8 +13,9 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; +use Twig\Node\Expression\ReturnNumberInterface; -class SubBinary extends AbstractBinary +class SubBinary extends AbstractBinary implements ReturnNumberInterface { public function operator(Compiler $compiler): Compiler { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/XorBinary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/XorBinary.php new file mode 100644 index 000000000..0e9173aba --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Binary/XorBinary.php @@ -0,0 +1,31 @@ +raw('xor'); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php index acd231e15..657a63719 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -13,6 +13,7 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; use Twig\Node\Node; /** @@ -20,21 +21,31 @@ use Twig\Node\Node; * * @author Fabien Potencier */ -class BlockReferenceExpression extends AbstractExpression +class BlockReferenceExpression extends AbstractExpression implements SupportDefinedTestInterface, CoercesChildrenToStringInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + + /** + * @param AbstractExpression $name + */ public function __construct(Node $name, ?Node $template, int $lineno) { + if (!$name instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $name::class); + } + $nodes = ['name' => $name]; if (null !== $template) { $nodes['template'] = $template; } - parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno); + parent::__construct($nodes, ['output' => false], $lineno); } public function compile(Compiler $compiler): void { - if ($this->getAttribute('is_defined_test')) { + if ($this->definedTest) { $this->compileTemplateCall($compiler, 'hasBlock'); } else { if ($this->getAttribute('output')) { @@ -50,17 +61,21 @@ class BlockReferenceExpression extends AbstractExpression } } + public function getStringCoercedChildNames(): array + { + // the template expression is resolved through the loader, which coerces it to a string + return $this->hasNode('template') ? ['template'] : []; + } + private function compileTemplateCall(Compiler $compiler, string $method): Compiler { if (!$this->hasNode('template')) { $compiler->write('$this'); } else { $compiler - ->write('$this->loadTemplate(') + ->write('$this->load(') ->subcompile($this->getNode('template')) ->raw(', ') - ->repr($this->getTemplateName()) - ->raw(', ') ->repr($this->getTemplateLine()) ->raw(')') ; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php index 6fc6f66e0..2a55ced49 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/CallExpression.php @@ -24,8 +24,11 @@ use Twig\Util\ReflectionCallable; abstract class CallExpression extends AbstractExpression { - private $reflector = null; + private $reflector; + /** + * @return void + */ protected function compileCallable(Compiler $compiler) { $twigCallable = $this->getTwigCallable(); @@ -97,6 +100,14 @@ abstract class CallExpression extends AbstractExpression $first = false; } + if (self::needsIsSandboxed($twigCallable)) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$this->env->hasExtension(\Twig\Extension\SandboxExtension::class) && $this->env->getExtension(\Twig\Extension\SandboxExtension::class)->getChecker()->isSandboxed($this->source)'); + $first = false; + } + foreach ($twigCallable->getArguments() as $argument) { if (!$first) { $compiler->raw(', '); @@ -114,7 +125,7 @@ abstract class CallExpression extends AbstractExpression } if ($this->hasNode('arguments')) { - $arguments = (new CallableArgumentsExtractor($this, $this->getTwigCallable()))->extractArguments($this->getNode('arguments')); + $arguments = (new CallableArgumentsExtractor($this, $this->getAttribute('twig_callable')))->extractArguments($this->getNode('arguments')); foreach ($arguments as $node) { if (!$first) { $compiler->raw(', '); @@ -128,7 +139,7 @@ abstract class CallExpression extends AbstractExpression } /** - * @deprecated since 3.12, use Twig\Util\CallableArgumentsExtractor::getArguments() instead + * @deprecated since Twig 3.12, use Twig\Util\CallableArgumentsExtractor::getArguments() instead */ protected function getArguments($callable, $arguments) { @@ -208,11 +219,10 @@ abstract class CallExpression extends AbstractExpression } elseif ($callableParameter->isDefaultValueAvailable()) { $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); } elseif ($callableParameter->isOptional()) { - if (empty($parameters)) { + if (!$parameters) { break; - } else { - $missingArguments[] = $name; } + $missingArguments[] = $name; } else { throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); } @@ -235,7 +245,7 @@ abstract class CallExpression extends AbstractExpression } } - if (!empty($parameters)) { + if ($parameters) { $unknownParameter = null; foreach ($parameters as $parameter) { if ($parameter instanceof Node) { @@ -258,7 +268,7 @@ abstract class CallExpression extends AbstractExpression } /** - * @deprecated since 3.12 + * @deprecated since Twig 3.12 */ protected function normalizeName(string $name): string { @@ -272,25 +282,9 @@ abstract class CallExpression extends AbstractExpression { $twigCallable = $this->getAttribute('twig_callable'); $rc = $this->reflectCallable($twigCallable); - $r = $rc->getReflector(); $callableName = $rc->getName(); - $parameters = $r->getParameters(); - if ($this->hasNode('node')) { - array_shift($parameters); - } - if ($twigCallable->needsCharset()) { - array_shift($parameters); - } - if ($twigCallable->needsEnvironment()) { - array_shift($parameters); - } - if ($twigCallable->needsContext()) { - array_shift($parameters); - } - foreach ($twigCallable->getArguments() as $argument) { - array_shift($parameters); - } + $parameters = $rc->getTwigParameters($this->hasNode('node')); $isPhpVariadic = false; if ($isVariadic) { @@ -318,6 +312,22 @@ abstract class CallExpression extends AbstractExpression return $this->reflector; } + /** + * @internal + * + * To be removed in 4.0 and replaced by $twigCallable->needsIsSandboxed(). + */ + public static function needsIsSandboxed(TwigCallableInterface $twigCallable): bool + { + if (method_exists($twigCallable, 'needsIsSandboxed')) { + return $twigCallable->needsIsSandboxed(); + } + + trigger_deprecation('twig/twig', '3.25', 'Not implementing the "needsIsSandboxed()" method in "%s" is deprecated. This method will be part of the "%s" interface in 4.0.', $twigCallable::class, TwigCallableInterface::class); + + return false; + } + /** * Overrides the Twig callable based on attributes (as potentially, attributes changed between the creation and the compilation of the node). * @@ -332,9 +342,10 @@ abstract class CallExpression extends AbstractExpression $this->getAttribute('name'), $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), [ + 'needs_is_sandboxed' => $this->hasAttribute('needs_is_sandboxed') ? $this->getAttribute('needs_is_sandboxed') : self::needsIsSandboxed($current), 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), ], - ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()), 'function' => (new TwigFunction( $this->hasAttribute('name') ? $this->getAttribute('name') : $current->getName(), $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), @@ -342,9 +353,10 @@ abstract class CallExpression extends AbstractExpression 'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(), 'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(), 'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(), + 'needs_is_sandboxed' => $this->hasAttribute('needs_is_sandboxed') ? $this->getAttribute('needs_is_sandboxed') : self::needsIsSandboxed($current), 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), ], - ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()), 'filter' => (new TwigFilter( $this->getAttribute('name'), $this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(), @@ -352,9 +364,10 @@ abstract class CallExpression extends AbstractExpression 'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(), 'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(), 'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(), + 'needs_is_sandboxed' => $this->hasAttribute('needs_is_sandboxed') ? $this->getAttribute('needs_is_sandboxed') : self::needsIsSandboxed($current), 'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(), ], - ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ?: $current->getArguments()), + ))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()), }); return $this->getAttribute('twig_callable'); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php index d7db99357..7fe309cf3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -13,11 +13,14 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Node\Expression\Ternary\ConditionalTernary; -class ConditionalExpression extends AbstractExpression +class ConditionalExpression extends AbstractExpression implements OperatorEscapeInterface { public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno) { + trigger_deprecation('twig/twig', '3.17', \sprintf('"%s" is deprecated; use "%s" instead.', __CLASS__, ConditionalTernary::class)); + parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); } @@ -42,4 +45,9 @@ class ConditionalExpression extends AbstractExpression ->raw('))'); } } + + public function getOperandNamesToEscape(): array + { + return ['expr2', 'expr3']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php index 2a8909d54..12dc0621f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ConstantExpression.php @@ -17,8 +17,10 @@ use Twig\Compiler; /** * @final */ -class ConstantExpression extends AbstractExpression +class ConstantExpression extends AbstractExpression implements SupportDefinedTestInterface, ReturnPrimitiveTypeInterface { + use SupportDefinedTestTrait; + public function __construct($value, int $lineno) { parent::__construct([], ['value' => $value], $lineno); @@ -26,6 +28,6 @@ class ConstantExpression extends AbstractExpression public function compile(Compiler $compiler): void { - $compiler->repr($this->getAttribute('value')); + $compiler->repr($this->definedTest ? true : $this->getAttribute('value')); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/EmptyExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/EmptyExpression.php new file mode 100644 index 000000000..768d1d64f --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/EmptyExpression.php @@ -0,0 +1,33 @@ +getName(); $default = new FilterExpression($node, $filter, $arguments, $node->getTemplateLine()); @@ -44,11 +53,11 @@ class DefaultFilter extends FilterExpression $default = new FilterExpression($node, new TwigFilter('default', [CoreExtension::class, 'default']), $arguments, $node->getTemplateLine()); } - if ('default' === $name && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { - $test = new DefinedTest(clone $node, new TwigTest('defined'), new Node(), $node->getTemplateLine()); - $false = \count($arguments) ? $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine()); + if ('default' === $name && ($node instanceof ContextVariable || $node instanceof GetAttrExpression)) { + $test = new DefinedTest(clone $node, new TwigTest('defined', null, ['always_allowed_in_sandbox' => true]), new EmptyNode(), $node->getTemplateLine()); + $false = \count($arguments) ? clone $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine()); - $node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine()); + $node = new ConditionalTernary($test, $default, $false, $node->getTemplateLine()); } else { $node = $default; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/EscapeFilter.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/EscapeFilter.php new file mode 100644 index 000000000..de29830a0 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/EscapeFilter.php @@ -0,0 +1,51 @@ +setAttribute('template_escaper', false); + } + + protected function compileCallable(Compiler $compiler): void + { + if (!$this->getAttribute('template_escaper')) { + parent::compileCallable($compiler); + + return; + } + + $compiler->raw('$this->escaper->escape'); + $this->compileArguments($compiler); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php index e115ab194..707e8ec24 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Filter/RawFilter.php @@ -13,6 +13,8 @@ namespace Twig\Node\Expression\Filter; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\EmptyNode; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Node; @@ -23,10 +25,17 @@ use Twig\TwigFilter; */ class RawFilter extends FilterExpression { + /** + * @param AbstractExpression $node + */ #[FirstClassTwigCallableReady] public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0) { - parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new Node(), $lineno ?: $node->getTemplateLine()); + if (!$node instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $node::class); + } + + parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new EmptyNode(), $lineno ?: $node->getTemplateLine()); } public function compile(Compiler $compiler): void diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php index efc91193e..eb947201d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FilterExpression.php @@ -14,15 +14,23 @@ namespace Twig\Node\Expression; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; use Twig\Node\NameDeprecation; use Twig\Node\Node; use Twig\TwigFilter; -class FilterExpression extends CallExpression +class FilterExpression extends CallExpression implements CoercesChildrenToStringInterface { + /** + * @param AbstractExpression $node + */ #[FirstClassTwigCallableReady] public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno) { + if (!$node instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $node::class); + } + if ($filter instanceof TwigFilter) { $name = $filter->getName(); $filterName = new ConstantExpression($name, $lineno); @@ -70,4 +78,10 @@ class FilterExpression extends CallExpression $this->compileCallable($compiler); } + + public function getStringCoercedChildNames(): array + { + // a filter may coerce its input and arguments to string (e.g. `upper`, `replace`) + return ['node', 'arguments']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php index 6215c6abf..5983962dc 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionExpression.php @@ -13,12 +13,16 @@ namespace Twig\Node\Expression; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; use Twig\Node\NameDeprecation; use Twig\Node\Node; use Twig\TwigFunction; -class FunctionExpression extends CallExpression +class FunctionExpression extends CallExpression implements SupportDefinedTestInterface, CoercesChildrenToStringInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + #[FirstClassTwigCallableReady] public function __construct(TwigFunction|string $function, Node $arguments, int $lineno) { @@ -29,7 +33,7 @@ class FunctionExpression extends CallExpression trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFunction" when creating a "%s" function of type "%s" is deprecated.', $name, static::class); } - parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function', 'is_defined_test' => false], $lineno); + parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function'], $lineno); if ($function instanceof TwigFunction) { $this->setAttribute('twig_callable', $function); @@ -44,6 +48,16 @@ class FunctionExpression extends CallExpression $this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12')); } + public function enableDefinedTest(): void + { + if ('constant' === $this->getAttribute('name')) { + $this->definedTest = true; + } + } + + /** + * @return void + */ public function compile(Compiler $compiler) { $name = $this->getAttribute('name'); @@ -59,10 +73,16 @@ class FunctionExpression extends CallExpression $this->setAttribute('twig_callable', $compiler->getEnvironment()->getFunction($name)); } - if ('constant' === $name && $this->getAttribute('is_defined_test')) { + if ('constant' === $name && $this->isDefinedTestEnabled()) { $this->getNode('arguments')->setNode('checkDefined', new ConstantExpression(true, $this->getTemplateLine())); } $this->compileCallable($compiler); } + + public function getStringCoercedChildNames(): array + { + // a function may coerce its arguments to string (the host PHP code is opaque to Twig) + return ['arguments']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php index 7e5c25ff4..170d0a13b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php @@ -1,5 +1,14 @@ getNode('arguments'); + if ($arguments->hasNode('enum')) { + $firstArgument = $arguments->getNode('enum'); + } elseif ($arguments->hasNode('0')) { + $firstArgument = $arguments->getNode('0'); + } else { + $firstArgument = null; + } + + if (!$firstArgument instanceof ConstantExpression || 1 !== \count($arguments)) { + parent::compile($compiler); + + return; + } + + $value = $firstArgument->getAttribute('value'); + + if (!\is_string($value)) { + throw new SyntaxError('The first argument of the "enum" function must be a string.', $this->getTemplateLine(), $this->getSourceContext()); + } + + if (!enum_exists($value)) { + throw new SyntaxError(\sprintf('The first argument of the "enum" function must be the name of an enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext()); + } + + if (!$cases = $value::cases()) { + throw new SyntaxError(\sprintf('The first argument of the "enum" function must be a non-empty enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext()); + } + + $compiler->raw(\sprintf('%s::%s', $value, $cases[0]->name)); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/RenderSandboxedFunction.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/RenderSandboxedFunction.php new file mode 100644 index 000000000..b2296aa68 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/FunctionNode/RenderSandboxedFunction.php @@ -0,0 +1,85 @@ +getNode('arguments'); + $outputStrategyName = self::getOutputStrategyName($arguments); + if (null === $outputStrategyName) { + throw new SyntaxError('Value for argument "output_strategy" is required for function "render_sandboxed".', $this->getTemplateLine(), $this->getSourceContext()); + } + + $outputStrategy = $arguments->getNode($outputStrategyName); + if (!$outputStrategy instanceof ConstantExpression) { + throw new SyntaxError('The "output_strategy" argument of the "render_sandboxed" function must be a non-empty literal string other than "all".', $outputStrategy->getTemplateLine(), $outputStrategy->getSourceContext()); + } + + $strategy = $outputStrategy->getAttribute('value'); + if (!\is_string($strategy) || '' === $strategy || 'all' === $strategy) { + throw new SyntaxError('The "output_strategy" argument of the "render_sandboxed" function must be a non-empty literal string other than "all".', $outputStrategy->getTemplateLine(), $outputStrategy->getSourceContext()); + } + + $runtimeArguments = clone $arguments; + $runtimeArguments->removeNode($outputStrategyName); + $this->setNode('arguments', $runtimeArguments); + try { + parent::compile($compiler); + } finally { + $this->setNode('arguments', $arguments); + } + } + + /** + * @internal + * + * @return string[] + */ + public static function getSafe(Node $arguments): array + { + $name = self::getOutputStrategyName($arguments); + if (null === $name) { + return []; + } + + $outputStrategy = $arguments->getNode($name); + if (!$outputStrategy instanceof ConstantExpression) { + return []; + } + + $outputStrategy = $outputStrategy->getAttribute('value'); + + return \is_string($outputStrategy) && '' !== $outputStrategy && 'all' !== $outputStrategy ? [$outputStrategy] : []; + } + + private static function getOutputStrategyName(Node $arguments): string|int|null + { + foreach ($arguments as $name => $argument) { + if (2 === $name || (\is_string($name) && 'outputstrategy' === strtolower(str_replace('_', '', $name)))) { + return $name; + } + } + + return null; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php index 29a446b88..665e4a465 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -14,29 +14,49 @@ namespace Twig\Node\Expression; use Twig\Compiler; use Twig\Extension\SandboxExtension; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Template; -class GetAttrExpression extends AbstractExpression +class GetAttrExpression extends AbstractExpression implements SupportDefinedTestInterface, CoercesChildrenToStringInterface { - public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno) + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + + /** + * @param ArrayExpression|NameExpression|null $arguments + */ + public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno, bool $nullSafe = false) { $nodes = ['node' => $node, 'attribute' => $attribute]; if (null !== $arguments) { $nodes['arguments'] = $arguments; } - parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); + if ($arguments && !$arguments instanceof ArrayExpression && !$arguments instanceof ContextVariable) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class)); + } + + parent::__construct($nodes, ['type' => $type, 'ignore_strict_check' => false, 'optimizable' => !$nullSafe, 'null_safe' => $nullSafe, 'is_short_circuited' => false, 'var_name' => null], $lineno); + } + + public function enableDefinedTest(): void + { + $this->definedTest = true; + $this->changeIgnoreStrictCheck($this); } public function compile(Compiler $compiler): void { $env = $compiler->getEnvironment(); + $arrayAccessSandbox = false; + $nullSafe = $this->getAttribute('null_safe'); // optimize array calls if ( $this->getAttribute('optimizable') && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) - && !$this->getAttribute('is_defined_test') + && !$this->definedTest && Template::ARRAY_CALL === $this->getAttribute('type') ) { $var = '$'.$compiler->getVarName(); @@ -45,26 +65,72 @@ class GetAttrExpression extends AbstractExpression ->subcompile($this->getNode('node')) ->raw(') && is_array(') ->raw($var) - ->raw(') || ') - ->raw($var) - ->raw(' instanceof ArrayAccess ? (') - ->raw($var) - ->raw('[') - ->subcompile($this->getNode('attribute')) - ->raw('] ?? null) : null)') ; - return; - } + if (!$env->hasExtension(SandboxExtension::class)) { + $compiler + ->raw(') || ') + ->raw($var) + ->raw(' instanceof ArrayAccess ? (') + ->raw($var) + ->raw('[') + ; + $this->compileArrayKey($compiler, $var); + $compiler->raw('] ?? null) : null)'); - $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, '); + return; + } + + $arrayAccessSandbox = true; + + $compiler + ->raw(') || ') + ->raw($var) + ->raw(' instanceof ArrayAccess && in_array(') + ->raw($var.'::class') + ->raw(', CoreExtension::ARRAY_LIKE_CLASSES, true) ? (') + ->raw($var) + ->raw('[') + ; + $this->compileArrayKey($compiler, $var); + $compiler->raw('] ?? null) : '); + } if ($this->getAttribute('ignore_strict_check')) { $this->getNode('node')->setAttribute('ignore_strict_check', true); } + if (null === $nullSafeNode = $nullSafe ? $this : null) { + $node = $this->getNode('node'); + while ($node instanceof self) { + if ($node->getAttribute('null_safe')) { + $nullSafeNode = $node; + break; + } + $node = $node->getNode('node'); + } + } + + $isShortCircuited = false; + if (null !== $nullSafeNode && !$nullSafeNode->isShortCircuited()) { + $compiler + ->raw('((null === ('.$nullSafeNode->getVarName($compiler).' = ') + ->subcompile($nullSafeNode->getNode('node')) + ->raw(')) ? null : '); + + $nullSafeNode->markAsShortCircuited(); + $isShortCircuited = true; + } + + $compiler->raw('CoreExtension::getAttribute($this->env, $this->source, '); + + if ($nullSafe) { + $compiler->raw($this->getVarName($compiler)); + } else { + $compiler->subcompile($this->getNode('node')); + } + $compiler - ->subcompile($this->getNode('node')) ->raw(', ') ->subcompile($this->getNode('attribute')) ; @@ -77,11 +143,88 @@ class GetAttrExpression extends AbstractExpression $compiler->raw(', ') ->repr($this->getAttribute('type')) - ->raw(', ')->repr($this->getAttribute('is_defined_test')) + ->raw(', ')->repr($this->definedTest) ->raw(', ')->repr($this->getAttribute('ignore_strict_check')) ->raw(', ')->repr($env->hasExtension(SandboxExtension::class)) ->raw(', ')->repr($this->getNode('node')->getTemplateLine()) ->raw(')') ; + + if ($arrayAccessSandbox) { + $compiler->raw(')'); + } + + if ($isShortCircuited) { + $compiler->raw(')'); + } + } + + public function getStringCoercedChildNames(): array + { + $names = []; + + // the host PHP method may coerce any argument to string + if ($this->hasNode('arguments')) { + $names[] = 'arguments'; + } + + // compileArrayKey() may coerce a Stringable key; expose it so the sandbox checks __toString() + if (Template::ARRAY_CALL === $this->getAttribute('type')) { + $names[] = 'attribute'; + } + + return $names; + } + + /** + * Normalizes a Stringable array key so optimized access matches getAttribute(): + * arrays and known string-keyed ArrayAccess implementations receive a string, + * while object-key implementations receive the object unchanged. + */ + private function compileArrayKey(Compiler $compiler, string $var): void + { + $attribute = $this->getNode('attribute'); + + if ($attribute instanceof ConstantExpression) { + $compiler->subcompile($attribute); + + return; + } + + $key = '$'.$compiler->getVarName(); + $compiler + ->raw('(('.$key.' = ') + ->subcompile($attribute) + ->raw(') instanceof \Stringable && (is_array('.$var.') || '.$var.' instanceof \ArrayObject || '.$var.' instanceof \ArrayIterator) ? (string) '.$key.' : '.$key.')') + ; + } + + private function changeIgnoreStrictCheck(self $node): void + { + $node->setAttribute('optimizable', false); + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof self) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } + + private function markAsShortCircuited(): void + { + $this->setAttribute('is_short_circuited', true); + } + + private function isShortCircuited(): bool + { + return $this->getAttribute('is_short_circuited'); + } + + private function getVarName(Compiler $compiler): string + { + if (null === $this->getAttribute('var_name')) { + $this->setAttribute('var_name', $compiler->getVarName()); + } + + return '$'.$this->getAttribute('var_name'); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php index 0a3c2e4f9..5509f7942 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/InlinePrint.php @@ -19,8 +19,13 @@ use Twig\Node\Node; */ final class InlinePrint extends AbstractExpression { + /** + * @param AbstractExpression $node + */ public function __construct(Node $node, int $lineno) { + trigger_deprecation('twig/twig', '3.16', \sprintf('The "%s" class is deprecated with no replacement.', static::class)); + parent::__construct(['node' => $node], [], $lineno); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ListExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ListExpression.php new file mode 100644 index 000000000..9b23857fc --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ListExpression.php @@ -0,0 +1,41 @@ + $items + */ + public function __construct(array $items, int $lineno) + { + parent::__construct($items, [], $lineno); + } + + public function compile(Compiler $compiler): void + { + foreach ($this as $i => $name) { + if ($i) { + $compiler->raw(', '); + } + + $compiler + ->raw('$__') + ->raw($name->getAttribute('name')) + ->raw('__') + ; + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MacroReferenceExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MacroReferenceExpression.php new file mode 100644 index 000000000..26d1fe65b --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MacroReferenceExpression.php @@ -0,0 +1,113 @@ + + */ +class MacroReferenceExpression extends AbstractExpression implements SupportDefinedTestInterface, CoercesChildrenToStringInterface +{ + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + + private bool $hasCallParentheses = true; + + /** + * @param string|AbstractExpression $name The bare macro name (a static identifier) or, for a dynamic + * call, an expression resolving to the macro name + */ + public function __construct(MacroVariable $template, string|AbstractExpression $name, AbstractExpression $arguments, int $lineno) + { + $nodes = ['template' => $template, 'arguments' => $arguments]; + $attributes = ['name' => null]; + + if (\is_string($name)) { + $attributes['name'] = $name; + } else { + $nodes['name'] = $name; + } + + parent::__construct($nodes, $attributes, $lineno); + } + + /** + * @internal + */ + public function setHasCallParentheses(bool $hasCallParentheses): void + { + $this->hasCallParentheses = $hasCallParentheses; + } + + /** + * @internal + */ + public function hasCallParentheses(): bool + { + return $this->hasCallParentheses; + } + + public function __clone() + { + // The template node must not be deep-cloned because its name is + // lazily generated during compilation and must stay in sync with + // the AssignMacroVariable that populates the $macros array. + $template = $this->nodes['template']; + parent::__clone(); + $this->nodes['template'] = $template; + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('template')); + + if ($this->definedTest) { + $compiler->raw('->has('); + $this->compileName($compiler); + $compiler->raw(', $context)'); + + return; + } + + $compiler->raw('->call('); + $this->compileName($compiler); + $compiler + ->raw(', ') + ->subcompile($this->getNode('arguments')) + ->raw(', $context, ') + ->repr($this->getTemplateLine()) + ->raw(', $this->source)') + ; + } + + public function getStringCoercedChildNames(): array + { + // Dynamic macro names are string-coerced at runtime. + return $this->hasNode('name') ? ['name'] : []; + } + + private function compileName(Compiler $compiler): void + { + // A dynamic macro name is resolved at runtime from a context value and + // string-coerced before the registry lookup. + if ($this->hasNode('name')) { + $compiler->raw('(string) ')->subcompile($this->getNode('name')); + } else { + $compiler->repr($this->getAttribute('name')); + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php index 01806f91d..00a7287ef 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -12,53 +12,47 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Node\Expression\Variable\ContextVariable; -class MethodCallExpression extends AbstractExpression +class MethodCallExpression extends AbstractExpression implements SupportDefinedTestInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno) { - parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); + trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MacroReferenceExpression::class); - if ($node instanceof NameExpression) { + parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno); + + if ($node instanceof ContextVariable) { $node->setAttribute('always_defined', true); } } public function compile(Compiler $compiler): void { - if ($this->getAttribute('is_defined_test')) { + if ($this->definedTest) { $compiler - ->raw('method_exists($macros[') + ->raw('$macros[') ->repr($this->getNode('node')->getAttribute('name')) - ->raw('], ') - ->repr($this->getAttribute('method')) - ->raw(')') + ->raw(']->has(') + ->repr(substr($this->getAttribute('method'), \strlen('macro_'))) + ->raw(', $context)') ; return; } $compiler - ->raw('CoreExtension::callMacro($macros[') + ->raw('$macros[') ->repr($this->getNode('node')->getAttribute('name')) - ->raw('], ') - ->repr($this->getAttribute('method')) - ->raw(', [') - ; - $first = true; - /** @var ArrayExpression */ - $args = $this->getNode('arguments'); - foreach ($args->getKeyValuePairs() as $pair) { - if (!$first) { - $compiler->raw(', '); - } - $first = false; - - $compiler->subcompile($pair['value']); - } - $compiler - ->raw('], ') + ->raw(']->call(') + ->repr(substr($this->getAttribute('method'), \strlen('macro_'))) + ->raw(', ') + ->subcompile($this->getNode('arguments')) + ->raw(', $context, ') ->repr($this->getTemplateLine()) - ->raw(', $context, $this->getSourceContext())'); + ->raw(', $this->getSourceContext())'); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php index 286aa5ae2..0e0367420 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NameExpression.php @@ -13,9 +13,13 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Node\Expression\Variable\ContextVariable; -class NameExpression extends AbstractExpression +class NameExpression extends AbstractExpression implements SupportDefinedTestInterface { + use SupportDefinedTestDeprecationTrait; + use SupportDefinedTestTrait; + private $specialVars = [ '_self' => '$this->getTemplateName()', '_context' => '$context', @@ -24,7 +28,11 @@ class NameExpression extends AbstractExpression public function __construct(string $name, int $lineno) { - parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); + if (self::class === static::class) { + trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', self::class, ContextVariable::class); + } + + parent::__construct([], ['name' => $name, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); } public function compile(Compiler $compiler): void @@ -33,8 +41,8 @@ class NameExpression extends AbstractExpression $compiler->addDebugInfo($this); - if ($this->getAttribute('is_defined_test')) { - if (isset($this->specialVars[$name])) { + if ($this->definedTest) { + if (isset($this->specialVars[$name]) || $this->getAttribute('always_defined')) { $compiler->repr(true); } elseif (\PHP_VERSION_ID >= 70400) { $compiler @@ -102,6 +110,6 @@ class NameExpression extends AbstractExpression { trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__); - return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); + return !isset($this->specialVars[$this->getAttribute('name')]) && !$this->definedTest; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php index 98630f7f0..4548519f5 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -12,23 +12,39 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Node\EmptyNode; use Twig\Node\Expression\Binary\AndBinary; +use Twig\Node\Expression\Binary\NullCoalesceBinary; use Twig\Node\Expression\Test\DefinedTest; use Twig\Node\Expression\Test\NullTest; use Twig\Node\Expression\Unary\NotUnary; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; use Twig\TwigTest; class NullCoalesceExpression extends ConditionalExpression { + /** + * @param AbstractExpression $left + * @param AbstractExpression $right + */ public function __construct(Node $left, Node $right, int $lineno) { - $test = new DefinedTest(clone $left, new TwigTest('defined'), new Node(), $left->getTemplateLine()); + trigger_deprecation('twig/twig', '3.17', \sprintf('"%s" is deprecated; use "%s" instead.', __CLASS__, NullCoalesceBinary::class)); + + if (!$left instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "left" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $left::class); + } + if (!$right instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "right" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $right::class); + } + + $test = new DefinedTest(clone $left, new TwigTest('defined', null, ['always_allowed_in_sandbox' => true]), new EmptyNode(), $left->getTemplateLine()); // for "block()", we don't need the null test as the return value is always a string if (!$left instanceof BlockReferenceExpression) { $test = new AndBinary( $test, - new NotUnary(new NullTest($left, new TwigTest('null'), new Node(), $left->getTemplateLine()), $left->getTemplateLine()), + new NotUnary(new NullTest($left, new TwigTest('null', null, ['always_allowed_in_sandbox' => true]), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()), $left->getTemplateLine() ); } @@ -45,7 +61,7 @@ class NullCoalesceExpression extends ConditionalExpression * cases might be implemented as an optimizer node visitor, but has not been done * as benefits are probably not worth the added complexity. */ - if ($this->getNode('expr2') instanceof NameExpression) { + if ($this->getNode('expr2') instanceof ContextVariable) { $this->getNode('expr2')->setAttribute('always_defined', true); $compiler ->raw('((') diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/OperatorEscapeInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/OperatorEscapeInterface.php new file mode 100644 index 000000000..06db6c616 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/OperatorEscapeInterface.php @@ -0,0 +1,25 @@ + 1. + * + * @author Fabien Potencier + */ +interface OperatorEscapeInterface +{ + /** + * @return string[] + */ + public function getOperandNamesToEscape(): array; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ReturnArrayInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ReturnArrayInterface.php new file mode 100644 index 000000000..a74864b5d --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/ReturnArrayInterface.php @@ -0,0 +1,16 @@ + + */ +trait SupportDefinedTestDeprecationTrait +{ + public function getAttribute($name, $default = null) + { + if ('is_defined_test' === $name) { + trigger_deprecation('twig/twig', '3.21', 'The "is_defined_test" attribute is deprecated, call "isDefinedTestEnabled()" instead.'); + + return $this->isDefinedTestEnabled(); + } + + return parent::getAttribute($name, $default); + } + + public function setAttribute(string $name, $value): void + { + if ('is_defined_test' === $name) { + trigger_deprecation('twig/twig', '3.21', 'The "is_defined_test" attribute is deprecated, call "enableDefinedTest()" instead.'); + + $this->definedTest = (bool) $value; + } else { + parent::setAttribute($name, $value); + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/SupportDefinedTestInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/SupportDefinedTestInterface.php new file mode 100644 index 000000000..450c691c5 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/SupportDefinedTestInterface.php @@ -0,0 +1,24 @@ + + */ +interface SupportDefinedTestInterface +{ + public function enableDefinedTest(): void; + + public function isDefinedTestEnabled(): bool; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/SupportDefinedTestTrait.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/SupportDefinedTestTrait.php new file mode 100644 index 000000000..4cf1a58d5 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/SupportDefinedTestTrait.php @@ -0,0 +1,27 @@ +definedTest = true; + } + + public function isDefinedTestEnabled(): bool + { + return $this->definedTest; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TempNameExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TempNameExpression.php index 004c704a5..d691ba51d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TempNameExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TempNameExpression.php @@ -12,20 +12,45 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Error\SyntaxError; class TempNameExpression extends AbstractExpression { - public function __construct(string $name, int $lineno) + // 4.0: re-evaluate "varargs" here once the implicit macro varargs bucket is removed + // (see MacroNode::VARARGS_NAME); the other names map to compiled variables ($context, + // $macros, $blocks, $this) and must stay. + public const RESERVED_NAMES = ['varargs', 'context', 'macros', 'blocks', 'this']; + + // Prefix applied to reserved names so their compiled PHP variables cannot clash + // with the internal ones ($varargs, $context, $macros, $blocks, $this) + public const RESERVED_NAME_PREFIX = "\u{035C}"; + + public function __construct(string|int|null $name, int $lineno) { + // All names supported by ExpressionParser::parsePrimaryExpression() should be excluded + if ($name && \in_array(strtolower($name), ['true', 'false', 'none', 'null'], true)) { + throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $name), $lineno); + } + + if (self::class === static::class) { + trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated.', self::class); + } + + if (null !== $name && (\is_int($name) || ctype_digit($name))) { + $name = (int) $name; + } elseif (\in_array($name, self::RESERVED_NAMES, true)) { + $name = self::RESERVED_NAME_PREFIX.$name; + } + parent::__construct([], ['name' => $name], $lineno); } public function compile(Compiler $compiler): void { - $compiler - ->raw('$_') - ->raw($this->getAttribute('name')) - ->raw('_') - ; + if (null === $this->getAttribute('name')) { + $this->setAttribute('name', $compiler->getVarName()); + } + + $compiler->raw('$'.$this->getAttribute('name')); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Ternary/ConditionalTernary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Ternary/ConditionalTernary.php new file mode 100644 index 000000000..3972755e0 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Ternary/ConditionalTernary.php @@ -0,0 +1,43 @@ + TrueTest::wrap($test), 'left' => $left, 'right' => $right], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->subcompile($this->getNode('test')) + ->raw(') ? (') + ->subcompile($this->getNode('left')) + ->raw(') : (') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function getOperandNamesToEscape(): array + { + return ['left', 'right']; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php index 24d3ee82c..73e43720f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -14,13 +14,8 @@ namespace Twig\Node\Expression\Test; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; use Twig\Error\SyntaxError; -use Twig\Node\Expression\ArrayExpression; -use Twig\Node\Expression\BlockReferenceExpression; -use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\FunctionExpression; -use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\MethodCallExpression; -use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\SupportDefinedTestInterface; use Twig\Node\Expression\TestExpression; use Twig\Node\Node; use Twig\TwigTest; @@ -37,26 +32,22 @@ use Twig\TwigTest; */ class DefinedTest extends TestExpression { + /** + * @param AbstractExpression $node + */ #[FirstClassTwigCallableReady] public function __construct(Node $node, TwigTest|string $name, ?Node $arguments, int $lineno) { - if ($node instanceof NameExpression) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof GetAttrExpression) { - $node->setAttribute('is_defined_test', true); - $this->changeIgnoreStrictCheck($node); - } elseif ($node instanceof BlockReferenceExpression) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { - $node->setAttribute('is_defined_test', true); - } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { - $node = new ConstantExpression(true, $node->getTemplateLine()); - } elseif ($node instanceof MethodCallExpression) { - $node->setAttribute('is_defined_test', true); - } else { + if (!$node instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $node::class); + } + + if (!$node instanceof SupportDefinedTestInterface) { throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); } + $node->enableDefinedTest(); + if (\is_string($name) && 'defined' !== $name) { trigger_deprecation('twig/twig', '3.12', 'Creating a "DefinedTest" instance with a test name that is not "defined" is deprecated.'); } @@ -64,18 +55,14 @@ class DefinedTest extends TestExpression parent::__construct($node, $name, $arguments, $lineno); } - private function changeIgnoreStrictCheck(GetAttrExpression $node) - { - $node->setAttribute('optimizable', false); - $node->setAttribute('ignore_strict_check', true); - - if ($node->getNode('node') instanceof GetAttrExpression) { - $this->changeIgnoreStrictCheck($node->getNode('node')); - } - } - public function compile(Compiler $compiler): void { $compiler->subcompile($this->getNode('node')); } + + public function getStringCoercedChildNames(): array + { + // the `defined` test does not coerce its node to string (it only inspects existence) + return []; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php index 90d58a49a..ab9dd416b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -33,4 +33,10 @@ class DivisiblebyTest extends TestExpression ->raw(')') ; } + + public function getStringCoercedChildNames(): array + { + // PHP `%` rejects Stringable with a TypeError, no coercion + return []; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php index a0e3ed62c..81a06aa69 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php @@ -32,4 +32,10 @@ class EvenTest extends TestExpression ->raw(')') ; } + + public function getStringCoercedChildNames(): array + { + // PHP `%` rejects Stringable with a TypeError, no coercion + return []; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/NullTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/NullTest.php index 45b54ae37..904aef927 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/NullTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/NullTest.php @@ -15,7 +15,7 @@ use Twig\Compiler; use Twig\Node\Expression\TestExpression; /** - * Checks that a variable is null. + * Checks that an expression is null. * * {{ var is none }} * @@ -31,4 +31,10 @@ class NullTest extends TestExpression ->raw(')') ; } + + public function getStringCoercedChildNames(): array + { + // `=== null` is strict, no coercion + return []; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/OddTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/OddTest.php index d56c71116..967c35311 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/OddTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/OddTest.php @@ -32,4 +32,10 @@ class OddTest extends TestExpression ->raw(')') ; } + + public function getStringCoercedChildNames(): array + { + // PHP `%` rejects Stringable with a TypeError, no coercion + return []; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php index f1e24db6f..cc4d723d0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -31,4 +31,10 @@ class SameasTest extends TestExpression ->raw(')') ; } + + public function getStringCoercedChildNames(): array + { + // `===` is strict, no coercion + return []; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/TrueTest.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/TrueTest.php new file mode 100644 index 000000000..f0c49d476 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Test/TrueTest.php @@ -0,0 +1,52 @@ + + */ +class TrueTest extends TestExpression +{ + public static function wrap(Node $node): Node + { + if ($node instanceof ReturnPrimitiveTypeInterface) { + return $node; + } + + return new self($node, new TwigTest('true', null, ['always_allowed_in_sandbox' => true]), null, $node->getTemplateLine()); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(($tmp = ') + ->subcompile($this->getNode('node')) + ->raw(') && $tmp instanceof Markup ? (string) $tmp : $tmp)') + ; + } + + public function getStringCoercedChildNames(): array + { + // the `(string)` cast only fires for Markup instances, whose __toString is always allowed + return []; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php index 080d85aaa..4d8123941 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/TestExpression.php @@ -13,15 +13,23 @@ namespace Twig\Node\Expression; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; +use Twig\Node\CoercesChildrenToStringInterface; use Twig\Node\NameDeprecation; use Twig\Node\Node; use Twig\TwigTest; -class TestExpression extends CallExpression +class TestExpression extends CallExpression implements ReturnBoolInterface, CoercesChildrenToStringInterface { #[FirstClassTwigCallableReady] + /** + * @param AbstractExpression $node + */ public function __construct(Node $node, string|TwigTest $test, ?Node $arguments, int $lineno) { + if (!$node instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $node::class); + } + $nodes = ['node' => $node]; if (null !== $arguments) { $nodes['arguments'] = $arguments; @@ -63,4 +71,21 @@ class TestExpression extends CallExpression $this->compileCallable($compiler); } + + public function getStringCoercedChildNames(): array + { + $names = []; + + // the `empty` test triggers an implicit string coercion through `CoreExtension::testEmpty()` + if ('empty' === $this->getAttribute('name')) { + $names[] = 'node'; + } + + // a test may coerce its arguments to string (the host PHP code is opaque to Twig) + if ($this->hasNode('arguments')) { + $names[] = 'arguments'; + } + + return $names; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php index e31e3f84b..09f3d0984 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php @@ -16,18 +16,32 @@ use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Node; -abstract class AbstractUnary extends AbstractExpression +abstract class AbstractUnary extends AbstractExpression implements UnaryInterface { + /** + * @param AbstractExpression $node + */ public function __construct(Node $node, int $lineno) { - parent::__construct(['node' => $node], [], $lineno); + if (!$node instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance argument to "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, $node::class); + } + + parent::__construct(['node' => $node], ['with_parentheses' => false], $lineno); } public function compile(Compiler $compiler): void { - $compiler->raw(' '); + if ($this->hasExplicitParentheses()) { + $compiler->raw('('); + } else { + $compiler->raw(' '); + } $this->operator($compiler); $compiler->subcompile($this->getNode('node')); + if ($this->hasExplicitParentheses()) { + $compiler->raw(')'); + } } abstract public function operator(Compiler $compiler): Compiler; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php index 55c11bacf..6bdf8800a 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php @@ -13,9 +13,17 @@ namespace Twig\Node\Expression\Unary; use Twig\Compiler; +use Twig\Node\Expression\ReturnBoolInterface; +use Twig\Node\Expression\Test\TrueTest; +use Twig\Node\Node; -class NotUnary extends AbstractUnary +class NotUnary extends AbstractUnary implements ReturnBoolInterface { + public function __construct(Node $node, int $lineno) + { + parent::__construct(TrueTest::wrap($node), $lineno); + } + public function operator(Compiler $compiler): Compiler { return $compiler->raw('!'); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/SpreadUnary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/SpreadUnary.php new file mode 100644 index 000000000..f99072c25 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/SpreadUnary.php @@ -0,0 +1,22 @@ +raw('...'); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/StringCastUnary.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/StringCastUnary.php new file mode 100644 index 000000000..87ea17ca8 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/StringCastUnary.php @@ -0,0 +1,22 @@ +raw('(string)'); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/UnaryInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/UnaryInterface.php new file mode 100644 index 000000000..b094ef4f4 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Unary/UnaryInterface.php @@ -0,0 +1,22 @@ + $var], ['global' => $global], $var->getTemplateLine()); + } + + public function compile(Compiler $compiler): void + { + /** @var MacroVariable $var */ + $var = $this->nodes['var']; + + $compiler + ->addDebugInfo($this) + ->write('$macros[') + ->string($var->getName($compiler)) + ->raw('] = ') + ; + + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->string($var->getName($compiler)) + ->raw('] = ') + ; + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Variable/AssignTemplateVariable.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Variable/AssignTemplateVariable.php new file mode 100644 index 000000000..4b82e240a --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Variable/AssignTemplateVariable.php @@ -0,0 +1,25 @@ +getAttribute('name')) { + $this->setAttribute('name', $compiler->getVarName()); + } + + return $this->getAttribute('name'); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getName($compiler); + + if ('_self' === $name) { + $compiler->raw('$this->getMacroNamespace()'); + } else { + $compiler + ->raw('($macros[') + ->string($name) + ->raw('] ?? $this->throwUninitializedMacroNamespace(') + ->repr($this->getTemplateLine()) + ->raw('))') + ; + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Variable/TemplateVariable.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Variable/TemplateVariable.php new file mode 100644 index 000000000..aa99bd49d --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Expression/Variable/TemplateVariable.php @@ -0,0 +1,27 @@ + + */ +#[YieldReady] +class ForElseNode extends Node +{ + public function __construct(Node $body, int $lineno) + { + parent::__construct(['body' => $body], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n") + ; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php index 2fc014792..fda40719f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ForNode.php @@ -15,7 +15,7 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\Variable\AssignContextVariable; /** * Represents a for node. @@ -27,9 +27,19 @@ class ForNode extends Node { private $loop; - public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno) + public function __construct(AssignContextVariable $keyTarget, AssignContextVariable $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno) { - $body = new Node([$body, $this->loop = new ForLoopNode($lineno)]); + $body = new Nodes([$body, $this->loop = new ForLoopNode($lineno)]); + + if (null !== $ifexpr) { + trigger_deprecation('twig/twig', '3.19', \sprintf('Passing not-null to the "ifexpr" argument of the "%s" constructor is deprecated.', static::class)); + } + + if (null !== $else && !$else instanceof ForElseNode) { + trigger_deprecation('twig/twig', '3.19', \sprintf('Not passing an instance of "%s" to the "else" argument of the "%s" constructor is deprecated.', ForElseNode::class, static::class)); + + $else = new ForElseNode($else, $else->getTemplateLine()); + } $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; if (null !== $else) { @@ -89,13 +99,7 @@ class ForNode extends Node ; if ($this->hasNode('else')) { - $compiler - ->write("if (!\$context['_iterated']) {\n") - ->indent() - ->subcompile($this->getNode('else')) - ->outdent() - ->write("}\n") - ; + $compiler->subcompile($this->getNode('else')); } $compiler->write("\$_parent = \$context['_parent'];\n"); @@ -111,6 +115,9 @@ class ForNode extends Node $compiler->raw(");\n"); // keep the values set in the inner context for variables defined in the outer context - $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); + $compiler + ->write("\$context = array_intersect_key(\$context, \$_parent);\n") + ->write("\$context += \$_parent;\n") + ; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php index 2af48fa81..c7a7f4b99 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/IfNode.php @@ -14,6 +14,7 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; +use Twig\Node\Expression\Test\TrueTest; /** * Represents an if node. @@ -25,6 +26,9 @@ class IfNode extends Node { public function __construct(Node $tests, ?Node $else, int $lineno) { + for ($i = 0, $count = \count($tests); $i < $count; $i += 2) { + $tests->setNode($i, TrueTest::wrap($tests->getNode((string) $i))); + } $nodes = ['tests' => $tests]; if (null !== $else) { $nodes['else'] = $else; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php index 9a6033f21..4d701f4b3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ImportNode.php @@ -14,7 +14,9 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\MacroVariable; /** * Represents an import node. @@ -22,54 +24,45 @@ use Twig\Node\Expression\NameExpression; * @author Fabien Potencier */ #[YieldReady] -class ImportNode extends Node +class ImportNode extends Node implements CoercesChildrenToStringInterface { - /** - * @param bool $global - */ - public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, $global = true) + public function __construct(AbstractExpression $expr, AbstractExpression|AssignMacroVariable $var, int $lineno) { - if (null === $global || \is_string($global)) { - trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); - $global = \func_num_args() > 4 ? func_get_arg(4) : true; - } elseif (!\is_bool($global)) { - throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($global))); + if (\func_num_args() > 3) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Passing more than 3 arguments to "%s()" is deprecated.', __METHOD__)); } - parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno); + if (!$var instanceof AssignMacroVariable) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Passing a "%s" instance as the second argument of "%s" is deprecated, pass a "%s" instead.', $var::class, __CLASS__, AssignMacroVariable::class)); + + $var = new AssignMacroVariable(new MacroVariable($var->getAttribute('name'), $lineno)); + } + + parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno); } public function compile(Compiler $compiler): void { - $compiler - ->addDebugInfo($this) - ->write('$macros[') - ->repr($this->getNode('var')->getAttribute('name')) - ->raw('] = ') - ; + $compiler->subcompile($this->getNode('var')); - if ($this->getAttribute('global')) { - $compiler - ->raw('$this->macros[') - ->repr($this->getNode('var')->getAttribute('name')) - ->raw('] = ') - ; - } - - if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { - $compiler->raw('$this'); + if ($this->getNode('expr') instanceof ContextVariable && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw('$this->getMacroNamespace()'); } else { $compiler - ->raw('$this->loadTemplate(') + ->raw('$this->load(') ->subcompile($this->getNode('expr')) ->raw(', ') - ->repr($this->getTemplateName()) - ->raw(', ') ->repr($this->getTemplateLine()) - ->raw(')->unwrap()') + ->raw(')->unwrap()->getMacroNamespace()') ; } $compiler->raw(";\n"); } + + public function getStringCoercedChildNames(): array + { + // the loader resolves the template-name expression by coercing it to a string + return ['expr']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php index 1c18292c5..3adf415e1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/IncludeNode.php @@ -22,7 +22,7 @@ use Twig\Node\Expression\AbstractExpression; * @author Fabien Potencier */ #[YieldReady] -class IncludeNode extends Node implements NodeOutputInterface +class IncludeNode extends Node implements NodeOutputInterface, CoercesChildrenToStringInterface { public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno) { @@ -42,13 +42,12 @@ class IncludeNode extends Node implements NodeOutputInterface $template = $compiler->getVarName(); $compiler - ->write(\sprintf("$%s = null;\n", $template)) ->write("try {\n") ->indent() ->write(\sprintf('$%s = ', $template)) ; - $this->addGetTemplate($compiler); + $this->addGetTemplate($compiler, $template); $compiler ->raw(";\n") @@ -56,13 +55,15 @@ class IncludeNode extends Node implements NodeOutputInterface ->write("} catch (LoaderError \$e) {\n") ->indent() ->write("// ignore missing template\n") + ->write(\sprintf("\$$template = null;\n", $template)) ->outdent() ->write("}\n") ->write(\sprintf("if ($%s) {\n", $template)) ->indent() - ->write(\sprintf('yield from $%s->unwrap()->yield(', $template)) ; + $compiler->write(\sprintf('yield from $%s->unwrap()->yield(', $template)); + $this->addTemplateArguments($compiler); $compiler ->raw(");\n") @@ -78,19 +79,23 @@ class IncludeNode extends Node implements NodeOutputInterface } } - protected function addGetTemplate(Compiler $compiler) + /** + * @return void + */ + protected function addGetTemplate(Compiler $compiler/* , string $template = '' */) { $compiler - ->write('$this->loadTemplate(') + ->raw('$this->load(') ->subcompile($this->getNode('expr')) ->raw(', ') - ->repr($this->getTemplateName()) - ->raw(', ') ->repr($this->getTemplateLine()) ->raw(')') ; } + /** + * @return void + */ protected function addTemplateArguments(Compiler $compiler) { if (!$this->hasNode('variables')) { @@ -107,4 +112,10 @@ class IncludeNode extends Node implements NodeOutputInterface $compiler->raw(')'); } } + + public function getStringCoercedChildNames(): array + { + // the loader resolves the template-name expression by coercing it to a string + return ['expr']; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/MacroDeclarationNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/MacroDeclarationNode.php new file mode 100644 index 000000000..edc33623f --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/MacroDeclarationNode.php @@ -0,0 +1,34 @@ + + * + * @internal + */ +#[YieldReady] +final class MacroDeclarationNode extends ConfigNode +{ + public function __construct(string $name, int $lineno) + { + parent::__construct($lineno); + + $this->setAttribute('name', $name); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php index 5a2543a9f..255ee8510 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/MacroNode.php @@ -14,74 +14,119 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Error\SyntaxError; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\TempNameExpression; +use Twig\Node\Expression\Variable\LocalVariable; +use Twig\TwigMacro; /** * Represents a macro node. * + * This class is considered final as of Twig 3.29 and will be final in Twig + * 4.0. + * + * @final since Twig 3.29 + * * @author Fabien Potencier */ #[YieldReady] class MacroNode extends Node { + // 4.0: "varargs" is only the implicit, deprecated extra-arguments bucket. Once the + // implicit bucket is removed, this constant and the reserved-name guard in the + // constructor go away (a regular argument may then be named "varargs"); only an + // explicit "...name" variadic remains. public const VARARGS_NAME = 'varargs'; /** - * @param BodyNode $body + * @param BodyNode $body + * @param ArrayExpression $arguments */ - public function __construct(string $name, Node $body, Node $arguments, int $lineno) + public function __construct(string $name, Node $body, Node $arguments, int $lineno, ?string $variadicName = null) { if (!$body instanceof BodyNode) { - trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); + trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated ("%s" given).', BodyNode::class, static::class, $body::class)); } - foreach ($arguments as $argumentName => $argument) { - if (self::VARARGS_NAME === $argumentName) { - throw new SyntaxError(\sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); + if (!$arguments instanceof ArrayExpression) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class)); + + $args = new ArrayExpression([], $arguments->getTemplateLine()); + foreach ($arguments as $n => $default) { + $args->addElement($default, new LocalVariable($n, $default->getTemplateLine())); } + $arguments = $args; } - parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno); + $seen = []; + foreach ($arguments->getKeyValuePairs() as $pair) { + $argName = $pair['key']->getAttribute('name'); + if (TempNameExpression::RESERVED_NAME_PREFIX.self::VARARGS_NAME === $argName) { + throw new SyntaxError(\sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext()); + } + if (null !== $variadicName && $variadicName === $this->stripReservedPrefix($argName)) { + throw new SyntaxError(\sprintf('The variadic argument "%s" in macro "%s" cannot have the same name as another argument.', $variadicName, $name), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext()); + } + if (isset($seen[$argName])) { + throw new SyntaxError(\sprintf('Argument "%s" is defined twice for macro "%s".', $this->stripReservedPrefix($argName), $name), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext()); + } + $seen[$argName] = true; + } + + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name, 'variadic_name' => $variadicName], $lineno); } public function compile(Compiler $compiler): void { + // 4.0 cleanup: only an explicitly declared variadic ("...name") gets a trailing + // "...$bucket" parameter and a context entry; a non-variadic macro must NOT emit + // "...$varargs" anymore (so extra arguments raise an error), and the implicit + // "varargs" context entry and VARARGS_NAME handling below go away. + $variadicName = $this->getAttribute('variadic_name'); + if (null === $variadicName) { + // Legacy implicit "varargs" bucket: to be removed in 4.0. + $bucketName = self::VARARGS_NAME; + $bucketVar = self::VARARGS_NAME; + } else { + $bucketName = $variadicName; + $bucketVar = \in_array($variadicName, TempNameExpression::RESERVED_NAMES, true) ? TempNameExpression::RESERVED_NAME_PREFIX.$variadicName : $variadicName; + } + + /** @var ArrayExpression $arguments */ + $arguments = $this->getNode('arguments'); + $compiler - ->addDebugInfo($this) - ->write(\sprintf('public function macro_%s(', $this->getAttribute('name'))) + ->raw('new \\'.TwigMacro::class.'(') + ->string($this->getAttribute('name')) + ->raw(', function (') ; - $count = \count($this->getNode('arguments')); - $pos = 0; - foreach ($this->getNode('arguments') as $name => $default) { + foreach ($arguments->getKeyValuePairs() as $pair) { $compiler - ->raw('$__'.$name.'__ = ') - ->subcompile($default) + ->subcompile($pair['key']) + ->raw(' = ') + ->subcompile($pair['value']) + ->raw(', ') ; - - if (++$pos < $count) { - $compiler->raw(', '); - } - } - - if ($count) { - $compiler->raw(', '); } $compiler - ->raw('...$__varargs__') - ->raw(")\n") - ->write("{\n") + ->raw('...$'.$bucketVar) + ->raw("): string|Markup {\n") ->indent() + ->addDebugInfo($this) ->write("\$macros = \$this->macros;\n") ->write("\$context = [\n") ->indent() ; - foreach ($this->getNode('arguments') as $name => $default) { + foreach ($arguments->getKeyValuePairs() as $pair) { + $var = $this->stripReservedPrefix($pair['key']->getAttribute('name')); $compiler ->write('') - ->string($name) - ->raw(' => $__'.$name.'__') + ->string($var) + ->raw(' => ') + ->subcompile($pair['key']) ->raw(",\n") ; } @@ -90,9 +135,9 @@ class MacroNode extends Node $compiler ->write('') - ->string(self::VARARGS_NAME) + ->string($bucketName) ->raw(' => ') - ->raw("\$__varargs__,\n") + ->raw('$'.$bucketVar.",\n") ->outdent() ->write("] + \$this->env->getGlobals();\n\n") ->write("\$blocks = [];\n\n") @@ -100,7 +145,23 @@ class MacroNode extends Node ->subcompile($node) ->raw("\n") ->outdent() - ->write("}\n\n") + ->write('}, ') + ; + + $signature = []; + foreach ($arguments->getKeyValuePairs() as $pair) { + $default = $pair['value']; + $signature[$this->stripReservedPrefix($pair['key']->getAttribute('name'))] = !($default->hasAttribute('is_implicit') && $default->getAttribute('is_implicit')); + } + + $compiler + ->repr($signature) + ->raw(', '.(null !== $variadicName ? 'true' : 'false').')') ; } + + private function stripReservedPrefix(string $name): string + { + return str_starts_with($name, TempNameExpression::RESERVED_NAME_PREFIX) ? substr($name, \strlen(TempNameExpression::RESERVED_NAME_PREFIX)) : $name; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/MacrosNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/MacrosNode.php new file mode 100644 index 000000000..d54c7f863 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/MacrosNode.php @@ -0,0 +1,81 @@ + + */ +#[YieldReady] +final class MacrosNode extends Node +{ + /** + * @param array $macros + */ + public function __construct(array $macros = []) + { + foreach ($macros as $name => $macro) { + if (!$macro instanceof MacroNode) { + throw new \InvalidArgumentException(\sprintf('Using "%s" for the macro "%s" of "%s" is not supported. You must pass a "%s" instance.', get_debug_type($macro), $name, static::class, MacroNode::class)); + } + } + + parent::__construct($macros); + } + + public function setNode(string $name, Node $node): void + { + if (!$node instanceof MacroNode) { + throw new \LogicException(\sprintf('A "%s" can only contain "%s" nodes; replacing the macro "%s" with a "%s" node is not supported.', static::class, MacroNode::class, $name, get_debug_type($node))); + } + + parent::setNode($name, $node); + } + + public function compile(Compiler $compiler): void + { + if (!\count($this)) { + return; + } + + $compiler + ->write("protected function loadDeclaredMacros(): array\n", "{\n") + ->indent() + ->write("return [\n") + ->indent() + ; + + /** @var MacroNode $macro */ + foreach ($this as $macro) { + $compiler + ->write('') + ->string($macro->getAttribute('name')) + ->raw(' => ') + ; + $compiler->subcompile($macro); + $compiler->raw(",\n"); + } + + $compiler + ->outdent() + ->write("];\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php index d2fb216b1..c39e3bcb1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/ModuleNode.php @@ -28,27 +28,42 @@ use Twig\Source; * @author Fabien Potencier */ #[YieldReady] -final class ModuleNode extends Node +final class ModuleNode extends Node implements CoercesChildrenToStringInterface { /** - * @param BodyNode $body + * @param BodyNode $body + * @param MacrosNode $macros */ public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source) { if (!$body instanceof BodyNode) { trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class)); } + if (!$macros instanceof MacrosNode) { + trigger_deprecation('twig/twig', '3.29', \sprintf('Not passing a "%s" instance as the "macros" argument of the "%s" constructor is deprecated.', MacrosNode::class, static::class)); + + $macros = new MacrosNode(iterator_to_array($macros)); + } + if (!$embeddedTemplates instanceof Node) { + trigger_deprecation('twig/twig', '3.21', \sprintf('Not passing a "%s" instance as the "embedded_templates" argument of the "%s" constructor is deprecated.', Node::class, static::class)); + + if (null !== $embeddedTemplates) { + $embeddedTemplates = new Nodes($embeddedTemplates); + } else { + $embeddedTemplates = new EmptyNode(); + } + } $nodes = [ 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits, - 'display_start' => new Node(), - 'display_end' => new Node(), - 'constructor_start' => new Node(), - 'constructor_end' => new Node(), - 'class_end' => new Node(), + 'display_start' => new Nodes(), + 'display_end' => new Nodes(), + 'constructor_start' => new Nodes(), + 'constructor_end' => new Nodes(), + 'class_end' => new Nodes(), ]; if (null !== $parent) { $nodes['parent'] = $parent; @@ -58,13 +73,15 @@ final class ModuleNode extends Node parent::__construct($nodes, [ 'index' => null, 'embedded_templates' => $embeddedTemplates, + 'strategy' => false, + 'escaper' => false, ], 1); // populate the template name of all node children $this->setSourceContext($source); } - public function setIndex($index) + public function setIndex($index): void { $this->setAttribute('index', $index); } @@ -78,7 +95,13 @@ final class ModuleNode extends Node } } - protected function compileTemplate(Compiler $compiler) + public function getStringCoercedChildNames(): array + { + // the parent expression is resolved through the loader, which coerces it to a string + return $this->hasNode('parent') ? ['parent'] : []; + } + + protected function compileTemplate(Compiler $compiler): void { if (!$this->getAttribute('index')) { $compiler->write('compileIsTraitable($compiler); + $this->compileGetDefaultEscapeStrategy($compiler); + $this->compileDebugInfo($compiler); $this->compileGetSourceContext($compiler); @@ -107,7 +132,7 @@ final class ModuleNode extends Node $this->compileClassFooter($compiler); } - protected function compileGetParent(Compiler $compiler) + protected function compileGetParent(Compiler $compiler): void { if (!$this->hasNode('parent')) { return; @@ -122,19 +147,18 @@ final class ModuleNode extends Node ; if ($parent instanceof ConstantExpression) { - $compiler->subcompile($parent); - } else { - $compiler - ->raw('$this->loadTemplate(') - ->subcompile($parent) - ->raw(', ') - ->repr($this->getSourceContext()->getName()) - ->raw(', ') - ->repr($parent->getTemplateLine()) - ->raw(')') - ; + // a constant parent never depends on the context, so resolve it once + $compiler->raw('$this->parent ??= '); } + $compiler + ->raw('$this->load(') + ->subcompile($parent) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') + ; + $compiler ->raw(";\n") ->outdent() @@ -142,7 +166,7 @@ final class ModuleNode extends Node ; } - protected function compileClassHeader(Compiler $compiler) + protected function compileClassHeader(Compiler $compiler): void { $compiler ->write("\n\n") @@ -154,11 +178,13 @@ final class ModuleNode extends Node ->write("use Twig\Error\RuntimeError;\n") ->write("use Twig\Extension\CoreExtension;\n") ->write("use Twig\Extension\SandboxExtension;\n") + ->write("use Twig\MacroNamespace;\n") ->write("use Twig\Markup;\n") ->write("use Twig\Sandbox\SecurityError;\n") ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n") ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedTestError;\n") ->write("use Twig\Source;\n") ->write("use Twig\Template;\n") ->write("use Twig\TemplateWrapper;\n") @@ -174,13 +200,19 @@ final class ModuleNode extends Node ->indent() ->write("private Source \$source;\n") ->write("/**\n") - ->write(" * @var array\n") + ->write(" * @var array\n") ->write(" */\n") - ->write("private array \$macros = [];\n\n") + ->write("private array \$macros = [];\n") ; + + if ($this->getAttribute('escaper')) { + $compiler->write("private \\Twig\\Runtime\\EscaperRuntime \$escaper;\n"); + } + + $compiler->raw("\n"); } - protected function compileConstructor(Compiler $compiler) + protected function compileConstructor(Compiler $compiler): void { $compiler ->write("public function __construct(Environment \$env)\n", "{\n") @@ -190,6 +222,10 @@ final class ModuleNode extends Node ->write("\$this->source = \$this->getSourceContext();\n\n") ; + if ($this->getAttribute('escaper')) { + $compiler->write("\$this->escaper = \$env->getRuntime('Twig\\Runtime\\EscaperRuntime');\n\n"); + } + // parent if (!$this->hasNode('parent')) { $compiler->write("\$this->parent = false;\n\n"); @@ -197,17 +233,16 @@ final class ModuleNode extends Node $countTraits = \count($this->getNode('traits')); if ($countTraits) { - // traits + $compiler->write("\$this->ensureTraitsAllowed();\n\n"); + foreach ($this->getNode('traits') as $i => $trait) { $node = $trait->getNode('template'); $compiler ->addDebugInfo($node) - ->write(\sprintf('$_trait_%s = $this->loadTemplate(', $i)) + ->write(\sprintf('$_trait_%s = $this->load(', $i)) ->subcompile($node) ->raw(', ') - ->repr($node->getTemplateName()) - ->raw(', ') ->repr($node->getTemplateLine()) ->raw(");\n") ->write(\sprintf("if (!\$_trait_%s->unwrap()->isTraitable()) {\n", $i)) @@ -228,11 +263,11 @@ final class ModuleNode extends Node ->string($key) ->raw("])) {\n") ->indent() - ->write("throw new RuntimeError('Block ") + ->write("throw new RuntimeError(sprintf('Block \"%s\" is not defined in trait \"%s\".', ") ->string($key) - ->raw(' is not defined in trait ') + ->raw(', ') ->subcompile($trait->getNode('template')) - ->raw(".', ") + ->raw('), ') ->repr($node->getTemplateLine()) ->raw(", \$this->source);\n") ->outdent() @@ -244,7 +279,11 @@ final class ModuleNode extends Node ->string($key) ->raw(\sprintf(']; unset($_trait_%s_blocks[', $i)) ->string($key) - ->raw("]);\n\n") + ->raw(']); $this->traitAliases[') + ->subcompile($value) + ->raw('] = ') + ->string($key) + ->raw(";\n\n") ; } } @@ -315,7 +354,7 @@ final class ModuleNode extends Node ; } - protected function compileDisplay(Compiler $compiler) + protected function compileDisplay(Compiler $compiler): void { $compiler ->write("protected function doDisplay(array \$context, array \$blocks = []): iterable\n", "{\n") @@ -331,11 +370,9 @@ final class ModuleNode extends Node $compiler->addDebugInfo($parent); if ($parent instanceof ConstantExpression) { $compiler - ->write('$this->parent = $this->loadTemplate(') + ->write('$this->parent = $this->load(') ->subcompile($parent) ->raw(', ') - ->repr($this->getSourceContext()->getName()) - ->raw(', ') ->repr($parent->getTemplateLine()) ->raw(");\n") ; @@ -353,7 +390,7 @@ final class ModuleNode extends Node $compiler->subcompile($this->getNode('display_end')); if (!$this->hasNode('parent')) { - $compiler->write("yield from [];\n"); + $compiler->write("return; yield;\n"); } $compiler @@ -362,7 +399,7 @@ final class ModuleNode extends Node ; } - protected function compileClassFooter(Compiler $compiler) + protected function compileClassFooter(Compiler $compiler): void { $compiler ->subcompile($this->getNode('class_end')) @@ -371,12 +408,12 @@ final class ModuleNode extends Node ; } - protected function compileMacros(Compiler $compiler) + protected function compileMacros(Compiler $compiler): void { $compiler->subcompile($this->getNode('macros')); } - protected function compileGetTemplateName(Compiler $compiler) + protected function compileGetTemplateName(Compiler $compiler): void { $compiler ->write("/**\n") @@ -392,7 +429,7 @@ final class ModuleNode extends Node ; } - protected function compileIsTraitable(Compiler $compiler) + protected function compileIsTraitable(Compiler $compiler): void { // A template can be used as a trait if: // * it has no parent @@ -410,7 +447,7 @@ final class ModuleNode extends Node } if (!\count($nodes)) { - $nodes = new Node([$nodes]); + $nodes = new Nodes([$nodes]); } foreach ($nodes as $node) { @@ -439,7 +476,27 @@ final class ModuleNode extends Node ; } - protected function compileDebugInfo(Compiler $compiler) + protected function compileGetDefaultEscapeStrategy(Compiler $compiler): void + { + if (false === $strategy = $this->getAttribute('strategy')) { + return; + } + + $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") + ->write("public function getDefaultEscapeStrategy(): string|false\n", "{\n") + ->indent() + ->write('return ') + ->repr($strategy) + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDebugInfo(Compiler $compiler): void { $compiler ->write("/**\n") @@ -453,7 +510,7 @@ final class ModuleNode extends Node ; } - protected function compileGetSourceContext(Compiler $compiler) + protected function compileGetSourceContext(Compiler $compiler): void { $compiler ->write("public function getSourceContext(): Source\n", "{\n") @@ -469,21 +526,4 @@ final class ModuleNode extends Node ->write("}\n") ; } - - protected function compileLoadTemplate(Compiler $compiler, $node, $var) - { - if ($node instanceof ConstantExpression) { - $compiler - ->write(\sprintf('%s = $this->loadTemplate(', $var)) - ->subcompile($node) - ->raw(', ') - ->repr($node->getTemplateName()) - ->raw(', ') - ->repr($node->getTemplateLine()) - ->raw(");\n") - ; - } else { - throw new \LogicException('Trait templates can only be constant nodes.'); - } - } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php index 2ccbcf610..bfa4f030f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Node.php @@ -20,6 +20,8 @@ use Twig\Source; * Represents a node in the AST. * * @author Fabien Potencier + * + * @implements \IteratorAggregate */ #[YieldReady] class Node implements \Countable, \IteratorAggregate @@ -33,6 +35,7 @@ class Node implements \Countable, \IteratorAggregate protected $tag; private $sourceContext; + private ?string $documentation = null; /** @var array */ private $nodeNameDeprecations = []; /** @var array */ @@ -45,9 +48,13 @@ class Node implements \Countable, \IteratorAggregate */ public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0) { + if (self::class === static::class) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Instantiating "%s" directly is deprecated; the class will become abstract in 4.0.', self::class)); + } + foreach ($nodes as $name => $node) { if (!$node instanceof self) { - throw new \InvalidArgumentException(\sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? $node::class : (null === $node ? 'null' : \gettype($node)), $name, static::class)); + throw new \InvalidArgumentException(\sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', get_debug_type($node), $name, static::class)); } } $this->nodes = $nodes; @@ -59,7 +66,7 @@ class Node implements \Countable, \IteratorAggregate } } - public function __toString() + public function __toString(): string { $repr = static::class; @@ -67,6 +74,10 @@ class Node implements \Countable, \IteratorAggregate $repr .= \sprintf("\n tag: %s", $this->tag); } + if (null !== $this->documentation) { + $repr .= \sprintf("\n documentation: %s", str_replace("\n", '\\n', $this->documentation)); + } + $attributes = []; foreach ($this->attributes as $name => $value) { if (\is_callable($value)) { @@ -99,6 +110,13 @@ class Node implements \Countable, \IteratorAggregate return $repr; } + public function __clone() + { + foreach ($this->nodes as $name => $node) { + $this->nodes[$name] = clone $node; + } + } + /** * @return void */ @@ -119,6 +137,19 @@ class Node implements \Countable, \IteratorAggregate return $this->tag; } + public function getDocumentation(): ?string + { + return $this->documentation; + } + + /** + * @internal + */ + public function setDocumentation(?string $documentation): void + { + $this->documentation = $documentation; + } + /** * @internal */ diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/NodeDocumentation.php b/data/web/inc/lib/vendor/twig/twig/src/Node/NodeDocumentation.php new file mode 100644 index 000000000..dd43e2b99 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/NodeDocumentation.php @@ -0,0 +1,51 @@ +getDocumentation()) { + $documentation[] = $comment; + } + } + if (null !== $comment = $node->getDocumentation()) { + $documentation[] = $comment; + } + + if ($documentation) { + $node->setDocumentation(implode("\n", $documentation)); + } + } + + public static function move(Node $source, Node $target): void + { + if (null !== $documentation = $source->getDocumentation()) { + self::prepend($target, $documentation); + $source->setDocumentation(null); + } + } + + private static function prepend(Node $node, string $documentation): void + { + if (null !== $currentDocumentation = $node->getDocumentation()) { + $documentation .= "\n".$currentDocumentation; + } + + $node->setDocumentation($documentation); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/Nodes.php b/data/web/inc/lib/vendor/twig/twig/src/Node/Nodes.php new file mode 100644 index 000000000..bd67053ab --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/Nodes.php @@ -0,0 +1,28 @@ + + */ +#[YieldReady] +final class Nodes extends Node +{ + public function __construct(array $nodes = [], int $lineno = 0) + { + parent::__construct($nodes, [], $lineno); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php index e3c23bbfa..0eec95d1f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/PrintNode.php @@ -15,6 +15,8 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\ReturnStringInterface; /** * Represents a node that outputs an expression. @@ -22,7 +24,7 @@ use Twig\Node\Expression\AbstractExpression; * @author Fabien Potencier */ #[YieldReady] -class PrintNode extends Node implements NodeOutputInterface +class PrintNode extends Node implements NodeOutputInterface, CoercesChildrenToStringInterface { public function __construct(AbstractExpression $expr, int $lineno) { @@ -34,11 +36,34 @@ class PrintNode extends Node implements NodeOutputInterface /** @var AbstractExpression */ $expr = $this->getNode('expr'); + $compiler->addDebugInfo($this); + + if ($expr->isGenerator()) { + $compiler->write('yield from '); + } else { + $compiler->write('yield '); + if (!$this->isString($expr)) { + $compiler->raw('(string) '); + } + } + $compiler - ->addDebugInfo($this) - ->write($expr->isGenerator() ? 'yield from ' : 'yield ') ->subcompile($expr) ->raw(";\n") ; } + + public function getStringCoercedChildNames(): array + { + return ['expr']; + } + + private function isString(AbstractExpression $expr): bool + { + if ($expr instanceof ReturnStringInterface) { + return true; + } + + return $expr instanceof ConstantExpression && !$expr->isDefinedTestEnabled() && \is_string($expr->getAttribute('value')); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php index d51cea44b..47feeb095 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/SandboxNode.php @@ -33,7 +33,7 @@ class SandboxNode extends Node ->addDebugInfo($this) ->write("if (!\$alreadySandboxed = \$this->sandbox->isSandboxed()) {\n") ->indent() - ->write("\$this->sandbox->enableSandbox();\n") + ->write("\$this->sandbox->setSandboxed(true);\n") ->outdent() ->write("}\n") ->write("try {\n") @@ -44,7 +44,7 @@ class SandboxNode extends Node ->indent() ->write("if (!\$alreadySandboxed) {\n") ->indent() - ->write("\$this->sandbox->disableSandbox();\n") + ->write("\$this->sandbox->setSandboxed(false);\n") ->outdent() ->write("}\n") ->outdent() diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php index 67725104e..7b063b00b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/SetNode.php @@ -33,9 +33,16 @@ class SetNode extends Node implements NodeCaptureInterface $safe = false; if ($capture) { $safe = true; - if ($values instanceof TextNode) { + // Node::class === get_class($values) should be removed in Twig 4.0 + if (($values instanceof Nodes || Node::class === $values::class) && !\count($values)) { + $values = new ConstantExpression('', $values->getTemplateLine()); + $capture = false; + } elseif ($values instanceof TextNode) { $values = new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine()); $capture = false; + } elseif ($values instanceof PrintNode && $values->getNode('expr') instanceof ConstantExpression) { + $values = $values->getNode('expr'); + $capture = false; } else { $values = new CaptureNode($values, $values->getTemplateLine()); } @@ -78,11 +85,23 @@ class SetNode extends Node implements NodeCaptureInterface $compiler->raw(']'); } else { if ($this->getAttribute('safe')) { - $compiler - ->raw("('' === \$tmp = ") - ->subcompile($this->getNode('values')) - ->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())") - ; + if ($this->getNode('values') instanceof ConstantExpression) { + if ('' === $this->getNode('values')->getAttribute('value')) { + $compiler->raw('""'); + } else { + $compiler + ->raw('new Markup(') + ->subcompile($this->getNode('values')) + ->raw(', $this->env->getCharset())') + ; + } + } else { + $compiler + ->raw("('' === \$tmp = ") + ->subcompile($this->getNode('values')) + ->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())") + ; + } } else { $compiler->subcompile($this->getNode('values')); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php index fae65fb2c..86071dab4 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/TextNode.php @@ -38,4 +38,20 @@ class TextNode extends Node implements NodeOutputInterface ->raw(";\n") ; } + + public function isBlank(): bool + { + if (ctype_space($this->getAttribute('data'))) { + return true; + } + + if (str_contains((string) $this, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + $t = substr($this->getAttribute('data'), 3); + if ('' === $t || ctype_space($t)) { + return true; + } + } + + return false; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/TypeNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/TypeNode.php new file mode 100644 index 000000000..bf4e8e382 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/TypeNode.php @@ -0,0 +1,31 @@ + + */ +#[YieldReady] +final class TypeNode extends Node +{ + /** + * @internal + */ + public function __construct(string $name, string $type, bool $optional, int $lineno) + { + parent::__construct([], ['name' => $name, 'type' => $type, 'optional' => $optional], $lineno); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php b/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php index ebb304d49..4009701fd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Node/TypesNode.php @@ -1,5 +1,14 @@ $types], $lineno); + $nodes = []; + foreach ($types as $name => $type) { + $nodes[$name] = new TypeNode($name, $type['type'], $type['optional'], $lineno); + } + + parent::__construct($nodes, ['mapping' => $types], $lineno); } + /** + * @return void + */ public function compile(Compiler $compiler) { // Don't compile anything. diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php index 5de35fd09..38b1ec9d0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -19,7 +19,7 @@ use Twig\Node\Node; * * @author Fabien Potencier * - * @deprecated since 3.9 (to be removed in 4.0) + * @deprecated since Twig 3.9 (to be removed in 4.0) */ abstract class AbstractNodeVisitor implements NodeVisitorInterface { diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/CorrectnessNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/CorrectnessNodeVisitor.php new file mode 100644 index 000000000..a2b719c62 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/CorrectnessNodeVisitor.php @@ -0,0 +1,259 @@ + + * + * @internal + */ +final class CorrectnessNodeVisitor implements NodeVisitorInterface +{ + private ?\WeakMap $rootNodes = null; + /** + * @var array + */ + private array $reportedMacroCallSites = []; + /** + * Stack of the output-wrapping tags ("if", "for", "set", ...) currently open; + * the top one is the nearest tag a "block" definition would be nested under. + * + * @var list + */ + private array $tagStack = []; + private bool $hasParent = false; + private int $blockDepth = 0; + private int $macroDepth = 0; + private int $capturingNodeDepth = 0; + private bool $hasExtends = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->enterModule($node); + + return $node; + } + + $this->enterScope($node); + + if ($node instanceof ConfigNode) { + $this->checkConfigTag($node); + } + + if ($node instanceof MacroReferenceExpression) { + $this->checkMacroCallParentheses($node); + } + + if ($node instanceof BlockReferenceNode) { + $this->checkBlockDefinitionNesting($node); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->resetState(); + + return $node; + } + + $this->leaveScope($node); + + return $node; + } + + public function getPriority(): int + { + return -255; + } + + private function enterModule(ModuleNode $node): void + { + $this->resetState(); + $this->rootNodes = new \WeakMap(); + $this->hasParent = $node->hasNode('parent'); + + foreach ($this->getRootNodes($node) as $n) { + if ($this->hasParent && !$this->isEmptyOutputNode($n)) { + throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $n->getTemplateLine(), $n->getSourceContext()); + } + $this->rootNodes[$n] = true; + } + } + + private function resetState(): void + { + $this->rootNodes = null; + $this->reportedMacroCallSites = []; + $this->tagStack = []; + $this->hasParent = false; + $this->blockDepth = 0; + $this->macroDepth = 0; + $this->capturingNodeDepth = 0; + $this->hasExtends = false; + } + + /** + * @return iterable + */ + private function getRootNodes(ModuleNode $node): iterable + { + $body = $node->getNode('body')->getNode('0'); + + // Parser::subparse() does not wrap the parsed nodes when there is only one, + // so $body can be a single "real" node instead of a Nodes container; in that + // case it is the only root node and must not be iterated over its children. + return $body instanceof Nodes || Node::class === $body::class ? $body : [$body]; + } + + private function enterScope(Node $node): void + { + if ($node instanceof NodeCaptureInterface) { + ++$this->capturingNodeDepth; + } + + if ($node instanceof BlockNode) { + ++$this->blockDepth; + } elseif ($node instanceof MacroNode) { + ++$this->macroDepth; + } elseif ($node->getNodeTag() && !$node instanceof BlockReferenceNode) { + $this->tagStack[] = $node; + } + } + + private function leaveScope(Node $node): void + { + if ($node instanceof NodeCaptureInterface) { + --$this->capturingNodeDepth; + } + + if ($node instanceof BlockNode) { + --$this->blockDepth; + } elseif ($node instanceof MacroNode) { + --$this->macroDepth; + } elseif ($node->getNodeTag() && !$node instanceof BlockReferenceNode) { + array_pop($this->tagStack); + } + } + + private function checkMacroCallParentheses(MacroReferenceExpression $node): void + { + if ($node->hasCallParentheses() || $node->isDefinedTestEnabled()) { + return; + } + + $sourceName = $node->getSourceContext()->getName(); + $line = $node->getTemplateLine(); + // A dynamic macro name is only known at runtime. + $name = $node->getAttribute('name'); + + // A single call site can be visited more than once: "??" reuses its left node, + // while "?:" and the "default" filter put a clone of it in the compiled tree. + $callSite = $sourceName."\0".$line."\0".$name; + if (isset($this->reportedMacroCallSites[$callSite])) { + return; + } + $this->reportedMacroCallSites[$callSite] = true; + + if (null === $name) { + trigger_deprecation('twig/twig', '3.29', 'Omitting parentheses when calling a macro is deprecated and will throw a SyntaxError in Twig 4.0; add parentheses after the macro name in "%s" at line %d.', $sourceName, $line); + } else { + trigger_deprecation('twig/twig', '3.29', 'Omitting parentheses when calling the macro "%s" is deprecated and will throw a SyntaxError in Twig 4.0; add parentheses after the macro name in "%s" at line %d.', $name, $sourceName, $line); + } + } + + private function checkConfigTag(ConfigNode $node): void + { + if ('extends' === $node->getNodeTag()) { + $this->checkExtendsTag($node); + } + + if (!isset($this->rootNodes[$node])) { + trigger_deprecation('twig/twig', '3.28', 'Using the "%s" tag outside the root of a template is deprecated in %s at line %d.', $node->getNodeTag(), $node->getSourceContext()->getName(), $node->getTemplateLine()); + } + } + + private function checkExtendsTag(ConfigNode $node): void + { + // "extends" inside a "block" or a "macro" has always been a hard error; keep it + if ($this->blockDepth) { + throw new SyntaxError('Cannot use "extend" in a block.', $node->getTemplateLine(), $node->getSourceContext()); + } + if ($this->macroDepth) { + throw new SyntaxError('Cannot use "extend" in a macro.', $node->getTemplateLine(), $node->getSourceContext()); + } + if ($this->hasExtends) { + throw new SyntaxError('Multiple extends tags are forbidden.', $node->getTemplateLine(), $node->getSourceContext()); + } + + $this->hasExtends = true; + } + + private function checkBlockDefinitionNesting(BlockReferenceNode $node): void + { + // A "block" definition nested under an output-wrapping tag is registered globally + // regardless of that tag, so the nesting is misleading. This only matters at the root + // of a child template's body: once inside a block, a macro, an output capture, or in + // a standalone template, the block is rendered in place and behaves like any other. + if (!$this->hasParent || $this->blockDepth || $this->macroDepth || $this->capturingNodeDepth || !$this->tagStack) { + return; + } + + $tag = $this->tagStack[array_key_last($this->tagStack)]; + throw new SyntaxError(\sprintf('A "block" tag cannot be under a "%s" tag (line %d).', $tag->getNodeTag(), $tag->getTemplateLine()), $node->getTemplateLine(), $node->getSourceContext()); + } + + /** + * Returns true if the node never outputs anything or if the output is empty. + */ + private function isEmptyOutputNode(Node $node): bool + { + if ($node instanceof NodeCaptureInterface) { + // a "block" tag in such a node will serve as a block definition AND be displayed in place as well + return true; + } + + // Can the text be considered "empty" (only whitespace)? + if ($node instanceof TextNode) { + return $node->isBlank(); + } + + if (!$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) { + return false; + } + + foreach ($node as $n) { + if (!$this->isEmptyOutputNode($n)) { + return false; + } + } + + return true; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php index 32f49ab1e..6063d8586 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -16,14 +16,15 @@ use Twig\Extension\EscaperExtension; use Twig\Node\AutoEscapeNode; use Twig\Node\BlockNode; use Twig\Node\BlockReferenceNode; -use Twig\Node\DoNode; -use Twig\Node\Expression\ConditionalExpression; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\Filter\EscapeFilter; use Twig\Node\Expression\FilterExpression; -use Twig\Node\Expression\InlinePrint; +use Twig\Node\Expression\OperatorEscapeInterface; use Twig\Node\ImportNode; use Twig\Node\ModuleNode; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Node\PrintNode; use Twig\NodeTraverser; @@ -40,6 +41,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface private $traverser; private $defaultStrategy = false; private $safeVars = []; + private bool $usesEscaper = false; public function __construct() { @@ -52,14 +54,16 @@ final class EscaperNodeVisitor implements NodeVisitorInterface if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) { $this->defaultStrategy = $defaultStrategy; } + $node->setAttribute('strategy', \is_string($this->defaultStrategy) ? $this->defaultStrategy : false); $this->safeVars = []; $this->blocks = []; + $this->usesEscaper = false; } elseif ($node instanceof AutoEscapeNode) { $this->statusStack[] = $node->getAttribute('value'); } elseif ($node instanceof BlockNode) { $this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping(); } elseif ($node instanceof ImportNode) { - $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + $this->safeVars[] = $node->getNode('var')->getNode('var')->getAttribute('name'); } return $node; @@ -68,18 +72,25 @@ final class EscaperNodeVisitor implements NodeVisitorInterface public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ModuleNode) { + $node->setAttribute('escaper', $this->usesEscaper); $this->defaultStrategy = false; $this->safeVars = []; $this->blocks = []; } elseif ($node instanceof FilterExpression) { + if ($node instanceof EscapeFilter) { + $this->useTemplateEscaper($node); + } + return $this->preEscapeFilterNode($node, $env); } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) { $expression = $node->getNode('expr'); - if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { - return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); + if ($expression instanceof OperatorEscapeInterface) { + $this->escapeConditional($expression, $env, $type); + } else { + $node->setNode('expr', $this->escapeExpression($expression, $env, $type)); } - return $this->escapePrintNode($node, $env, $type); + return $node; } if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { @@ -91,55 +102,25 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return $node; } - private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool + /** + * @param AbstractExpression&OperatorEscapeInterface $expression + */ + private function escapeConditional($expression, Environment $env, string $type): void { - $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); - $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); - - return $expr2Safe !== $expr3Safe; + foreach ($expression->getOperandNamesToEscape() as $name) { + /** @var AbstractExpression $operand */ + $operand = $expression->getNode($name); + if ($operand instanceof OperatorEscapeInterface) { + $this->escapeConditional($operand, $env, $type); + } else { + $expression->setNode($name, $this->escapeExpression($operand, $env, $type)); + } + } } - private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression + private function escapeExpression(AbstractExpression $expression, Environment $env, string $type): AbstractExpression { - // convert "echo a ? b : c" to "a ? echo b : echo c" recursively - $expr2 = $expression->getNode('expr2'); - if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { - $expr2 = $this->unwrapConditional($expr2, $env, $type); - } else { - $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type); - } - $expr3 = $expression->getNode('expr3'); - if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) { - $expr3 = $this->unwrapConditional($expr3, $env, $type); - } else { - $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type); - } - - return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); - } - - private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node - { - $expression = $node->getNode('node'); - - if ($this->isSafeFor($type, $expression, $env)) { - return $node; - } - - return new InlinePrint($this->getEscaperFilter($env, $type, $expression), $node->getTemplateLine()); - } - - private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node - { - $expression = $node->getNode('expr'); - - if ($this->isSafeFor($type, $expression, $env)) { - return $node; - } - - $class = \get_class($node); - - return new $class($this->getEscaperFilter($env, $type, $expression), $node->getTemplateLine()); + return $this->isSafeFor($type, $expression, $env) ? $expression : $this->getEscaperFilter($env, $type, $expression); } private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression @@ -156,6 +137,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return $filter; } + /** @var AbstractExpression $node */ $node = $filter->getNode('node'); if ($this->isSafeFor($type, $node, $env)) { return $filter; @@ -166,11 +148,11 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return $filter; } - private function isSafeFor(string $type, Node $expression, Environment $env): bool + private function isSafeFor(string $type, AbstractExpression $expression, Environment $env): bool { $safe = $this->safeAnalysis->getSafe($expression); - if (null === $safe) { + if (!$safe) { if (null === $this->traverser) { $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]); } @@ -181,10 +163,13 @@ final class EscaperNodeVisitor implements NodeVisitorInterface $safe = $this->safeAnalysis->getSafe($expression); } - return \in_array($type, $safe) || \in_array('all', $safe); + return \in_array($type, $safe, true) || \in_array('all', $safe, true); } - private function needEscaping() + /** + * @return string|false + */ + private function needEscaping(): string|bool { if (\count($this->statusStack)) { return $this->statusStack[\count($this->statusStack) - 1]; @@ -193,13 +178,25 @@ final class EscaperNodeVisitor implements NodeVisitorInterface return $this->defaultStrategy ?: false; } - private function getEscaperFilter(Environment $env, string $type, Node $node): FilterExpression + private function getEscaperFilter(Environment $env, string $type, AbstractExpression $node): FilterExpression { $line = $node->getTemplateLine(); $filter = $env->getFilter('escape'); - $args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + $args = new Nodes([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + $class = $filter->getNodeClass(); + $expression = new $class($node, $filter, $args, $line); - return new FilterExpression($node, $filter, $args, $line); + if ($expression instanceof EscapeFilter) { + $this->useTemplateEscaper($expression); + } + + return $expression; + } + + private function useTemplateEscaper(EscapeFilter $filter): void + { + $filter->setAttribute('template_escaper', true); + $this->usesEscaper = true; } public function getPriority(): int diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php deleted file mode 100644 index 01d5a997f..000000000 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * @internal - */ -final class MacroAutoImportNodeVisitor implements NodeVisitorInterface -{ - private $inAModule = false; - private $hasMacroCalls = false; - - public function enterNode(Node $node, Environment $env): Node - { - if ($node instanceof ModuleNode) { - $this->inAModule = true; - $this->hasMacroCalls = false; - } - - return $node; - } - - public function leaveNode(Node $node, Environment $env): Node - { - if ($node instanceof ModuleNode) { - $this->inAModule = false; - if ($this->hasMacroCalls) { - $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, true)); - } - } elseif ($this->inAModule) { - if ( - $node instanceof GetAttrExpression - && $node->getNode('node') instanceof NameExpression - && '_self' === $node->getNode('node')->getAttribute('name') - && $node->getNode('attribute') instanceof ConstantExpression - ) { - $this->hasMacroCalls = true; - - $name = $node->getNode('attribute')->getAttribute('value'); - $node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine()); - $node->setAttribute('safe', true); - } - } - - return $node; - } - - public function getPriority(): int - { - // we must be ran before auto-escaping - return -10; - } -} diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php index a943f45c3..b778ba40e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -17,8 +17,8 @@ use Twig\Node\Expression\BlockReferenceExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\ParentExpression; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\ForNode; use Twig\Node\IncludeNode; use Twig\Node\Node; @@ -137,13 +137,13 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface // when do we need to add the loop variable back? // the loop variable is referenced for the current loop - elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) { + elseif ($node instanceof ContextVariable && 'loop' === $node->getAttribute('name')) { $node->setAttribute('always_defined', true); $this->addLoopToCurrent(); } // optimize access to loop targets - elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) { + elseif ($node instanceof ContextVariable && \in_array($node->getAttribute('name'), $this->loopsTargets, true)) { $node->setAttribute('always_defined', true); } @@ -173,7 +173,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface || 'parent' === $node->getNode('attribute')->getAttribute('value') ) && (true === $this->loops[0]->getAttribute('with_loop') - || ($node->getNode('node') instanceof NameExpression + || ($node->getNode('node') instanceof ContextVariable && 'loop' === $node->getNode('node')->getAttribute('name') ) ) diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php index 07672164e..b5d88e303 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -13,14 +13,15 @@ namespace Twig\NodeVisitor; use Twig\Environment; use Twig\Node\Expression\BlockReferenceExpression; -use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\MethodCallExpression; -use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\OperatorEscapeInterface; use Twig\Node\Expression\ParentExpression; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; /** @@ -28,50 +29,44 @@ use Twig\Node\Node; */ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface { - private $data = []; + /** + * @var \WeakMap + */ + private \WeakMap $data; private $safeVars = []; + public function __construct() + { + $this->data = new \WeakMap(); + } + public function setSafeVars(array $safeVars): void { $this->safeVars = $safeVars; } + /** + * @return array + */ public function getSafe(Node $node) { - $hash = spl_object_hash($node); - if (!isset($this->data[$hash])) { - return; + $safe = $this->data[$node] ?? []; + + if (\in_array('html_attr', $safe, true)) { + $safe[] = 'html'; + $safe[] = 'html_attr_relaxed'; } - foreach ($this->data[$hash] as $bucket) { - if ($bucket['key'] !== $node) { - continue; - } - - if (\in_array('html_attr', $bucket['value'])) { - $bucket['value'][] = 'html'; - } - - return $bucket['value']; + if (\in_array('html_attr_relaxed', $safe, true)) { + $safe[] = 'html'; } + + return $safe; } private function setSafe(Node $node, array $safe): void { - $hash = spl_object_hash($node); - if (isset($this->data[$hash])) { - foreach ($this->data[$hash] as &$bucket) { - if ($bucket['key'] === $node) { - $bucket['value'] = $safe; - - return; - } - } - } - $this->data[$hash][] = [ - 'key' => $node, - 'value' => $safe, - ]; + $this->data[$node] = $safe; } public function enterNode(Node $node, Environment $env): Node @@ -90,10 +85,15 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface } elseif ($node instanceof ParentExpression) { // parent block is safe by definition $this->setSafe($node, ['all']); - } elseif ($node instanceof ConditionalExpression) { - // intersect safeness of both operands - $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); - $this->setSafe($node, $safe); + } elseif ($node instanceof OperatorEscapeInterface) { + // intersect safeness of operands + $operands = $node->getOperandNamesToEscape(); + if (2 < \count($operands)) { + throw new \LogicException(\sprintf('Operators with more than 2 operands are not supported yet, got %d.', \count($operands))); + } elseif (2 === \count($operands)) { + $safe = $this->intersectSafe($this->getSafe($node->getNode($operands[0])), $this->getSafe($node->getNode($operands[1]))); + $this->setSafe($node, $safe); + } } elseif ($node instanceof FilterExpression) { // filter expression is safe when the filter is safe if ($node->hasAttribute('twig_callable')) { @@ -106,11 +106,14 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface if ($filter) { $safe = $filter->getSafe($node->getNode('arguments')); if (null === $safe) { + trigger_deprecation('twig/twig', '3.16', 'The "%s::getSafe()" method should not return "null" anymore, return "[]" instead.', $filter::class); + $safe = []; + } + + if (!$safe) { $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); } $this->setSafe($node, $safe); - } else { - $this->setSafe($node, []); } } elseif ($node instanceof FunctionExpression) { // function expression is safe when the function is safe @@ -122,41 +125,37 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface } if ($function) { - $this->setSafe($node, $function->getSafe($node->getNode('arguments'))); - } else { - $this->setSafe($node, []); + $safe = $function->getSafe($node->getNode('arguments')); + if (null === $safe) { + trigger_deprecation('twig/twig', '3.16', 'The "%s::getSafe()" method should not return "null" anymore, return "[]" instead.', $function::class); + $safe = []; + } + $this->setSafe($node, $safe); } - } elseif ($node instanceof MethodCallExpression) { - if ($node->getAttribute('safe')) { - $this->setSafe($node, ['all']); - } else { - $this->setSafe($node, []); - } - } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { + } elseif ($node instanceof MethodCallExpression || $node instanceof MacroReferenceExpression) { + // all macro calls are safe + $this->setSafe($node, ['all']); + } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof ContextVariable) { $name = $node->getNode('node')->getAttribute('name'); - if (\in_array($name, $this->safeVars)) { + if (\in_array($name, $this->safeVars, true)) { $this->setSafe($node, ['all']); - } else { - $this->setSafe($node, []); } - } else { - $this->setSafe($node, []); } return $node; } - private function intersectSafe(?array $a = null, ?array $b = null): array + private function intersectSafe(array $a, array $b): array { - if (null === $a || null === $b) { + if (!$a || !$b) { return []; } - if (\in_array('all', $a)) { + if (\in_array('all', $a, true)) { return $b; } - if (\in_array('all', $b)) { + if (\in_array('all', $b, true)) { return $a; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php index 37e184a3e..15001f1b2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -15,16 +15,22 @@ use Twig\Environment; use Twig\Node\CheckSecurityCallNode; use Twig\Node\CheckSecurityNode; use Twig\Node\CheckToStringNode; -use Twig\Node\Expression\Binary\ConcatBinary; +use Twig\Node\CoercesChildrenToStringInterface; +use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\Binary\RangeBinary; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\OperatorEscapeInterface; +use Twig\Node\Expression\TestExpression; +use Twig\Node\Expression\Unary\SpreadUnary; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\ModuleNode; use Twig\Node\Node; -use Twig\Node\PrintNode; -use Twig\Node\SetNode; +use Twig\Node\Nodes; +use Twig\TokenParser\TokenParserInterface; +use Twig\TwigCallableInterface; +use Twig\Util\CallableParameters; /** * @author Fabien Potencier @@ -40,7 +46,8 @@ final class SandboxNodeVisitor implements NodeVisitorInterface private $filters; /** @var array */ private $functions; - private $needsToStringWrap = false; + /** @var array */ + private $tests; public function enterNode(Node $node, Environment $env): Node { @@ -49,51 +56,64 @@ final class SandboxNodeVisitor implements NodeVisitorInterface $this->tags = []; $this->filters = []; $this->functions = []; - - return $node; + $this->tests = []; } elseif ($this->inAModule) { // look for tags - if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()]) && !$this->isTagAlwaysAllowedInSandbox($env, $node->getNodeTag())) { $this->tags[$node->getNodeTag()] = $node->getTemplateLine(); } // look for filters - if ($node instanceof FilterExpression && !isset($this->filters[$node->getAttribute('name')])) { - $this->filters[$node->getAttribute('name')] = $node->getTemplateLine(); + if ($node instanceof FilterExpression && !isset($this->filters[$name = $node->getAttribute('name')]) && !$this->isFilterAlwaysAllowedInSandbox($env, $node)) { + $this->filters[$name] = $node->getTemplateLine(); } // look for functions - if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) { - $this->functions[$node->getAttribute('name')] = $node->getTemplateLine(); + if ($node instanceof FunctionExpression && !isset($this->functions[$name = $node->getAttribute('name')]) && !$this->isFunctionAlwaysAllowedInSandbox($env, $node)) { + $this->functions[$name] = $node->getTemplateLine(); + } + + // look for tests + if ($node instanceof TestExpression && !isset($this->tests[$name = $node->getAttribute('name')]) && !$this->isTestAlwaysAllowedInSandbox($env, $node)) { + $this->tests[$name] = $node->getTemplateLine(); + } + + // look for functions whose parser callable replaced the FunctionExpression + // with a specialized node (e.g. `parent`, `block`, `attribute`); the + // original function name was stashed by FunctionExpressionParser. + if ($node->hasAttribute('sandboxed_function_name')) { + $name = $node->getAttribute('sandboxed_function_name'); + if (!isset($this->functions[$name]) && !$this->isSandboxedFunctionAlwaysAllowedInSandbox($env, $node, $name)) { + $this->functions[$name] = $node->getTemplateLine(); + } } // the .. operator is equivalent to the range() function - if ($node instanceof RangeBinary && !isset($this->functions['range'])) { + if ($node instanceof RangeBinary && !isset($this->functions['range']) && !$this->isFunctionNameAlwaysAllowedInSandbox($env, 'range')) { $this->functions['range'] = $node->getTemplateLine(); } + } - if ($node instanceof PrintNode) { - $this->needsToStringWrap = true; - $this->wrapNode($node, 'expr'); - } + // wrap children that the node itself will string-coerce at runtime; + // applies to ModuleNode (`parent` slot for {% extends %}) too + if ($this->inAModule && $node instanceof CoercesChildrenToStringInterface) { + $params = CallableParameters::fromNode($node, $env); + foreach ($node->getStringCoercedChildNames() as $childName) { + // For Filter/Function/Test calls, consult the PHP callable + // signature: skip wrapping arguments whose param type cannot + // implicitly string-coerce (e.g. `int`, a `final` value object). + if (null !== $params && 'arguments' === $childName) { + $this->wrapArguments($node, $params); - if ($node instanceof SetNode && !$node->getAttribute('capture')) { - $this->needsToStringWrap = true; - } - - // wrap outer nodes that can implicitly call __toString() - if ($this->needsToStringWrap) { - if ($node instanceof ConcatBinary) { - $this->wrapNode($node, 'left'); - $this->wrapNode($node, 'right'); + continue; } - if ($node instanceof FilterExpression) { - $this->wrapNode($node, 'node'); - $this->wrapArrayNode($node, 'arguments'); - } - if ($node instanceof FunctionExpression) { - $this->wrapArrayNode($node, 'arguments'); + if (null !== $params && 'node' === $childName && $node instanceof FilterExpression) { + // The filter's input value maps to the first PHP parameter. + if (isset($params[0]) && CallableParameters::isStringCoercionSafe($params[0]->getType(), $params[0]->getDeclaringClass())) { + continue; + } } + $this->wrapNode($node, $childName); } } @@ -105,31 +125,167 @@ final class SandboxNodeVisitor implements NodeVisitorInterface if ($node instanceof ModuleNode) { $this->inAModule = false; - $node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')])); - $node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')])); - } elseif ($this->inAModule) { - if ($node instanceof PrintNode || $node instanceof SetNode) { - $this->needsToStringWrap = false; - } + $node->setNode('constructor_start', new Nodes([new CheckSecurityCallNode(), $node->getNode('constructor_start')])); + $node->setNode('class_end', new Nodes([new CheckSecurityNode($this->filters, $this->tags, $this->functions, $this->tests), $node->getNode('class_end')])); } return $node; } + /** + * Wraps each entry in the `arguments` slot only when the corresponding + * PHP parameter type can implicitly string-coerce. + * + * @param list<\ReflectionParameter> $params parameters relative to the + * first template argument (for + * filters and tests: starting + * after the `node`/input + * parameter) + */ + private function wrapArguments(Node $node, array $params): void + { + $arguments = $node->getNode('arguments'); + if (!$arguments instanceof Nodes && !$arguments instanceof ArrayExpression) { + $this->wrapNode($node, 'arguments'); + + return; + } + + // Filters and tests pass their input value (`node`) as the first PHP + // param, so their template arguments start at offset 1. + $positional = \array_slice($params, $node->hasNode('node') ? 1 : 0); + $variadic = null; + $byName = []; + foreach ($positional as $p) { + if ($p->isVariadic()) { + $variadic = $p; + break; + } + $byName[$this->normalizeName($p->getName())] ??= $p; + } + + $positionalIdx = 0; + foreach ($arguments as $key => $_) { + if (\is_int($key)) { + $param = $positional[$positionalIdx] ?? $variadic; + if (null !== $param && !$param->isVariadic()) { + ++$positionalIdx; + } + } else { + $param = $byName[$this->normalizeName($key)] ?? $variadic; + } + + if (null !== $param && CallableParameters::isStringCoercionSafe($param->getType(), $param->getDeclaringClass())) { + continue; + } + $this->wrapNode($arguments, (string) $key); + } + } + + private function normalizeName(string $name): string + { + return strtolower(str_replace('_', '', $name)); + } + private function wrapNode(Node $node, string $name): void { $expr = $node->getNode($name); - if (($expr instanceof NameExpression || $expr instanceof GetAttrExpression) && !$expr->isGenerator()) { + // `_self` is internal: it compiles to `$this->getTemplateName()` and is always a string + if ($expr instanceof ContextVariable && '_self' === $expr->getAttribute('name')) { + return; + } + if (($expr instanceof ContextVariable || $expr instanceof GetAttrExpression) && !$expr->isGenerator()) { + $node->setNode($name, new CheckToStringNode($expr)); + } elseif ($expr instanceof SpreadUnary) { + $expr->setNode('node', new CheckToStringNode($expr->getNode('node'), true)); + } elseif ($expr instanceof ArrayExpression || $expr instanceof Nodes) { + foreach ($expr as $name => $_) { + $this->wrapNode($expr, $name); + } + } elseif ($expr instanceof OperatorEscapeInterface) { + foreach ($expr->getOperandNamesToEscape() as $operandName) { + $this->wrapNode($expr, $operandName); + } + } elseif ($expr instanceof FilterExpression || $expr instanceof FunctionExpression) { $node->setNode($name, new CheckToStringNode($expr)); } } - private function wrapArrayNode(Node $node, string $name): void + private function isTagAlwaysAllowedInSandbox(Environment $env, string $name): bool { - $args = $node->getNode($name); - foreach ($args as $name => $_) { - $this->wrapNode($args, $name); + if (null === $parser = $env->getTokenParser($name)) { + return false; } + + return self::isAlwaysAllowedInSandbox($parser); + } + + private function isFilterAlwaysAllowedInSandbox(Environment $env, FilterExpression $node): bool + { + if ($node->hasAttribute('twig_callable')) { + $filter = $node->getAttribute('twig_callable'); + } elseif (null === $filter = $env->getFilter($node->getAttribute('name'))) { + return false; + } + + return self::isAlwaysAllowedInSandbox($filter); + } + + private function isFunctionAlwaysAllowedInSandbox(Environment $env, FunctionExpression $node): bool + { + if ($node->hasAttribute('twig_callable')) { + $function = $node->getAttribute('twig_callable'); + } elseif (null === $function = $env->getFunction($node->getAttribute('name'))) { + return false; + } + + return self::isAlwaysAllowedInSandbox($function); + } + + private function isTestAlwaysAllowedInSandbox(Environment $env, TestExpression $node): bool + { + if ($node->hasAttribute('twig_callable')) { + $test = $node->getAttribute('twig_callable'); + } elseif (null === $test = $env->getTest($node->getAttribute('name'))) { + return false; + } + + return self::isAlwaysAllowedInSandbox($test); + } + + private function isSandboxedFunctionAlwaysAllowedInSandbox(Environment $env, Node $node, string $name): bool + { + if ($node->hasAttribute('sandboxed_function')) { + $function = $node->getAttribute('sandboxed_function'); + } elseif (null === $function = $env->getFunction($name)) { + return false; + } + + return self::isAlwaysAllowedInSandbox($function); + } + + private function isFunctionNameAlwaysAllowedInSandbox(Environment $env, string $name): bool + { + if (null === $function = $env->getFunction($name)) { + return false; + } + + return self::isAlwaysAllowedInSandbox($function); + } + + /** + * @param TwigCallableInterface|TokenParserInterface $subject + */ + private static function isAlwaysAllowedInSandbox($subject): bool + { + if (method_exists($subject, 'isAlwaysAllowedInSandbox')) { + return $subject->isAlwaysAllowedInSandbox(); + } + + $interface = $subject instanceof TokenParserInterface ? TokenParserInterface::class : TwigCallableInterface::class; + trigger_deprecation('twig/twig', '3.28', 'Not implementing the "isAlwaysAllowedInSandbox()" method in "%s" is deprecated. This method will be part of the "%s" interface in 4.0.', $subject::class, $interface); + + return false; } public function getPriority(): int diff --git a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php index 4b190b414..4d6cf60a0 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php @@ -30,7 +30,7 @@ final class YieldNotReadyNodeVisitor implements NodeVisitorInterface public function enterNode(Node $node, Environment $env): Node { - $class = \get_class($node); + $class = $node::class; if ($node instanceof AbstractExpression || isset($this->yieldReadyNodes[$class])) { return $node; @@ -38,10 +38,10 @@ final class YieldNotReadyNodeVisitor implements NodeVisitorInterface if (!$this->yieldReadyNodes[$class] = (bool) (new \ReflectionClass($class))->getAttributes(YieldReady::class)) { if ($this->useYield) { - throw new \LogicException(\sprintf('You cannot enable the "use_yield" option of Twig as node "%s" is not marked as ready for it; please make it ready and then flag it with the #[YieldReady] attribute.', $class)); + throw new \LogicException(\sprintf('You cannot enable the "use_yield" option of Twig as node "%s" is not marked as ready for it; please make it ready and then flag it with the #[\Twig\Attribute\YieldReady] attribute.', $class)); } - trigger_deprecation('twig/twig', '3.9', 'Twig node "%s" is not marked as ready for using "yield" instead of "echo"; please make it ready and then flag it with the #[YieldReady] attribute.', $class); + trigger_deprecation('twig/twig', '3.9', 'Twig node "%s" is not marked as ready for using "yield" instead of "echo"; please make it ready and then flag it with the #[\Twig\Attribute\YieldReady] attribute.', $class); } return $node; diff --git a/data/web/inc/lib/vendor/twig/twig/src/OperatorPrecedenceChange.php b/data/web/inc/lib/vendor/twig/twig/src/OperatorPrecedenceChange.php new file mode 100644 index 000000000..31ebaef48 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/OperatorPrecedenceChange.php @@ -0,0 +1,34 @@ + + * + * @deprecated since Twig 1.20 Use Twig\ExpressionParser\PrecedenceChange instead + */ +class OperatorPrecedenceChange extends PrecedenceChange +{ + public function __construct( + private string $package, + private string $version, + private int $newPrecedence, + ) { + trigger_deprecation('twig/twig', '3.21', 'The "%s" class is deprecated since Twig 3.21. Use "%s" instead.', self::class, PrecedenceChange::class); + + parent::__construct($package, $version, $newPrecedence); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Parser.php b/data/web/inc/lib/vendor/twig/twig/src/Parser.php index 40370bb1b..ddd7fe2cd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Parser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Parser.php @@ -13,15 +13,25 @@ namespace Twig; use Twig\Error\SyntaxError; +use Twig\ExpressionParser\ExpressionParserInterface; +use Twig\ExpressionParser\ExpressionParsers; +use Twig\ExpressionParser\ExpressionParserType; +use Twig\ExpressionParser\InfixExpressionParserInterface; +use Twig\ExpressionParser\Prefix\LiteralExpressionParser; +use Twig\ExpressionParser\PrefixExpressionParserInterface; use Twig\Node\BlockNode; use Twig\Node\BlockReferenceNode; use Twig\Node\BodyNode; +use Twig\Node\EmptyNode; use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\MacroNode; +use Twig\Node\MacrosNode; use Twig\Node\ModuleNode; use Twig\Node\Node; -use Twig\Node\NodeCaptureInterface; -use Twig\Node\NodeOutputInterface; +use Twig\Node\NodeDocumentation; +use Twig\Node\Nodes; use Twig\Node\PrintNode; use Twig\Node\TextNode; use Twig\TokenParser\TokenParserInterface; @@ -33,32 +43,54 @@ use Twig\Util\ReflectionCallable; class Parser { private $stack = []; + private ?\WeakMap $expressionRefs = null; private $stream; private $parent; private $visitors; private $expressionParser; private $blocks; private $blockStack; + /** @var list */ + private array $documentationTargets = []; private $macros; private $importedSymbols; private $traits; private $embeddedTemplates = []; + private int $lastEmbedIndex = 0; private $varNameSalt = 0; + private $ignoreUnknownTwigCallables = false; + private ExpressionParsers $parsers; public function __construct( private Environment $env, ) { + $this->parsers = $env->getExpressionParsers(); + } + + public function getEnvironment(): Environment + { + return $this->env; } public function getVarName(): string { + trigger_deprecation('twig/twig', '3.15', 'The "%s()" method is deprecated.', __METHOD__); + return \sprintf('__internal_parse_%d', $this->varNameSalt++); } + /** + * @throws SyntaxError + */ public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode { + // reset on root parse() calls only, so the counter spans nested/reentrant parses + if (!$this->stack) { + $this->lastEmbedIndex = 0; + } + $vars = get_object_vars($this); - unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']); + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['lastEmbedIndex'], $vars['varNameSalt']); $this->stack[] = $vars; // node visitors @@ -66,25 +98,19 @@ class Parser $this->visitors = $this->env->getNodeVisitors(); } - if (null === $this->expressionParser) { - $this->expressionParser = new ExpressionParser($this, $this->env); - } - $this->stream = $stream; $this->parent = null; $this->blocks = []; + $this->documentationTargets = []; $this->macros = []; $this->traits = []; $this->blockStack = []; $this->importedSymbols = [[]]; $this->embeddedTemplates = []; + $this->expressionRefs = new \WeakMap(); try { $body = $this->subparse($test, $dropNeedle); - - if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { - $body = new Node(); - } } catch (SyntaxError $e) { if (!$e->getSourceContext()) { $e->setSourceContext($this->stream->getSourceContext()); @@ -95,9 +121,23 @@ class Parser } throw $e; + } finally { + $this->expressionRefs = null; } - $node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); + if ($this->parent) { + $body = $this->cleanupBodyForChildTemplates($body); + } + + $node = new ModuleNode( + new BodyNode([$body]), + $this->parent, + $this->blocks ? new Nodes($this->blocks) : new EmptyNode(), + new MacrosNode($this->macros), + $this->traits ? new Nodes($this->traits) : new EmptyNode(), + $this->embeddedTemplates ? new Nodes($this->embeddedTemplates) : new EmptyNode(), + $stream->getSourceContext(), + ); $traverser = new NodeTraverser($this->env, $this->visitors); @@ -114,29 +154,50 @@ class Parser return $node; } + public function shouldIgnoreUnknownTwigCallables(): bool + { + return $this->ignoreUnknownTwigCallables; + } + + public function subparseIgnoreUnknownTwigCallables($test, bool $dropNeedle = false): void + { + $previous = $this->ignoreUnknownTwigCallables; + $this->ignoreUnknownTwigCallables = true; + try { + $this->subparse($test, $dropNeedle); + } finally { + $this->ignoreUnknownTwigCallables = $previous; + } + } + + /** + * @throws SyntaxError + */ public function subparse($test, bool $dropNeedle = false): Node { $lineno = $this->getCurrentToken()->getLine(); $rv = []; while (!$this->stream->isEOF()) { - switch ($this->getCurrentToken()->getType()) { - case Token::TEXT_TYPE: + switch (true) { + case $this->stream->getCurrent()->test(Token::TEXT_TYPE): $token = $this->stream->next(); $rv[] = new TextNode($token->getValue(), $token->getLine()); break; - case Token::VAR_START_TYPE: + case $this->stream->getCurrent()->test(Token::VAR_START_TYPE): $token = $this->stream->next(); - $expr = $this->expressionParser->parseExpression(); + $expr = $this->parseExpression(); $this->stream->expect(Token::VAR_END_TYPE); - $rv[] = new PrintNode($expr, $token->getLine()); + $node = new PrintNode($expr, $token->getLine()); + NodeDocumentation::add($node, $token); + $rv[] = $node; break; - case Token::BLOCK_START_TYPE: - $this->stream->next(); + case $this->stream->getCurrent()->test(Token::BLOCK_START_TYPE): + $startToken = $this->stream->next(); $token = $this->getCurrentToken(); - if (Token::NAME_TYPE !== $token->getType()) { + if (!$token->test(Token::NAME_TYPE)) { throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); } @@ -149,7 +210,7 @@ class Parser return $rv[0]; } - return new Node($rv, [], $lineno); + return new Nodes($rv, $lineno); } if (!$subparser = $this->env->getTokenParser($token->getValue())) { @@ -171,11 +232,22 @@ class Parser $this->stream->next(); $subparser->setParser($this); - $node = $subparser->parse($token); + $documentationTargetIndex = \count($this->documentationTargets); + $this->documentationTargets[] = null; + try { + $node = $subparser->parse($token); + $documentationTarget = $this->documentationTargets[$documentationTargetIndex]; + } finally { + array_pop($this->documentationTargets); + } if (!$node) { trigger_deprecation('twig/twig', '3.12', 'Returning "null" from "%s" is deprecated and forbidden by "TokenParserInterface".', $subparser::class); } else { $node->setNodeTag($subparser->getTag()); + NodeDocumentation::add($node, $startToken); + if (null !== $documentationTarget && $node !== $documentationTarget) { + NodeDocumentation::move($node, $documentationTarget); + } $rv[] = $node; } break; @@ -189,7 +261,7 @@ class Parser return $rv[0]; } - return new Node($rv, [], $lineno); + return new Nodes($rv, $lineno); } public function getBlockStack(): array @@ -199,6 +271,9 @@ class Parser return $this->blockStack; } + /** + * @return string|null + */ public function peekBlockStack() { return $this->blockStack[\count($this->blockStack) - 1] ?? null; @@ -244,8 +319,24 @@ class Parser return isset($this->macros[$name]); } + public function setDocumentationTarget(Node $node): void + { + if (null === $index = array_key_last($this->documentationTargets)) { + throw new \LogicException('A documentation target can only be set while parsing a tag.'); + } + if (null !== $this->documentationTargets[$index]) { + throw new \LogicException('The documentation target for a tag can only be set once.'); + } + + $this->documentationTargets[$index] = $node; + } + public function setMacro(string $name, MacroNode $node): void { + if (isset($this->macros[$name])) { + trigger_deprecation('twig/twig', '3.29', 'Defining the macro "%s" more than once in "%s" is deprecated and will throw a SyntaxError in Twig 4.0 (previous definition at line %d, new definition at line %d). The last definition is used in Twig 3.', $name, $this->stream->getSourceContext()->getName(), $this->macros[$name]->getTemplateLine(), $node->getTemplateLine()); + } + $this->macros[$name] = $node; } @@ -261,18 +352,30 @@ class Parser return \count($this->traits) > 0; } + /** + * @return void + */ public function embedTemplate(ModuleNode $template) { - $template->setIndex(mt_rand()); + $template->setIndex(++$this->lastEmbedIndex); $this->embeddedTemplates[] = $template; } - public function addImportedSymbol(string $type, string $alias, ?string $name = null, ?AbstractExpression $node = null): void + public function addImportedSymbol(string $type, string $alias, ?string $name = null, AbstractExpression|AssignMacroVariable|null $internalRef = null): void { - $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; + if ($internalRef && !$internalRef instanceof AssignMacroVariable) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance as an internal reference is deprecated ("%s" given).', __METHOD__, AssignMacroVariable::class, $internalRef::class); + + $internalRef = new AssignMacroVariable(new MacroVariable($internalRef->getAttribute('name'), $internalRef->getTemplateLine()), $internalRef->getAttribute('global')); + } + + $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $internalRef]; } + /** + * @return array{name: string, node: AssignMacroVariable|null}|null + */ public function getImportedSymbol(string $type, string $alias) { // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) @@ -294,11 +397,42 @@ class Parser array_shift($this->importedSymbols); } + /** + * @deprecated since Twig 3.21 + */ public function getExpressionParser(): ExpressionParser { + trigger_deprecation('twig/twig', '3.21', 'Method "%s()" is deprecated, use "parseExpression()" instead.', __METHOD__); + + if (null === $this->expressionParser) { + $this->expressionParser = new ExpressionParser($this, $this->env); + } + return $this->expressionParser; } + public function parseExpression(int $precedence = 0): AbstractExpression + { + $token = $this->getCurrentToken(); + if ($token->test(Token::OPERATOR_TYPE) && $ep = $this->parsers->getByName(PrefixExpressionParserInterface::class, $token->getValue())) { + $this->getStream()->next(); + $expr = $ep->parse($this, $token); + $this->checkPrecedenceDeprecations($ep, $expr); + } else { + $expr = $this->parsers->getByClass(LiteralExpressionParser::class)->parse($this, $token); + } + + $token = $this->getCurrentToken(); + while ($token->test(Token::OPERATOR_TYPE) && ($ep = $this->parsers->getByName(InfixExpressionParserInterface::class, $token->getValue())) && $ep->getPrecedence() >= $precedence) { + $this->getStream()->next(); + $expr = $ep->parse($this, $expr, $token); + $this->checkPrecedenceDeprecations($ep, $expr); + $token = $this->getCurrentToken(); + } + + return $expr; + } + public function getParent(): ?Node { trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__); @@ -306,18 +440,29 @@ class Parser return $this->parent; } + /** + * @return bool + */ public function hasInheritance() { return $this->parent || 0 < \count($this->traits); } - public function setParent(?Node $parent): void + public function setParent(?Node $parent, bool $throwOnMultiple = true): void { if (null === $parent) { trigger_deprecation('twig/twig', '3.12', 'Passing "null" to "%s()" is deprecated.', __METHOD__); } + if (null !== $parent && !$parent instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.24', 'Passing a "%s" instance to "%s()" is deprecated, pass an "AbstractExpression" instance instead.', $parent::class, __METHOD__); + } + if (null !== $this->parent) { + if (!$throwOnMultiple) { + return; + } + throw new SyntaxError('Multiple extends tags are forbidden.', $parent->getTemplateLine(), $parent->getSourceContext()); } @@ -334,50 +479,171 @@ class Parser return $this->stream->getCurrent(); } - private function filterBodyNodes(Node $node, bool $nested = false): ?Node + public function getFunction(string $name, int $line): TwigFunction { - // check that the body does not contain non-empty output nodes - if ( - ($node instanceof TextNode && !ctype_space($node->getAttribute('data'))) - || (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) - ) { - if (str_contains((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { - $t = substr($node->getAttribute('data'), 3); - if ('' === $t || ctype_space($t)) { - // bypass empty nodes starting with a BOM - return null; + try { + $function = $this->env->getFunction($name); + } catch (SyntaxError $e) { + if (!$this->shouldIgnoreUnknownTwigCallables()) { + throw $e; + } + + $function = null; + } + + if (!$function) { + if ($this->shouldIgnoreUnknownTwigCallables()) { + return new TwigFunction($name, static fn () => ''); + } + $e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); + + throw $e; + } + + if ($function->isDeprecated()) { + $src = $this->stream->getSourceContext(); + $function->triggerDeprecation($src->getPath() ?: $src->getName(), $line); + } + + return $function; + } + + public function getFilter(string $name, int $line): TwigFilter + { + try { + $filter = $this->env->getFilter($name); + } catch (SyntaxError $e) { + if (!$this->shouldIgnoreUnknownTwigCallables()) { + throw $e; + } + + $filter = null; + } + if (!$filter) { + if ($this->shouldIgnoreUnknownTwigCallables()) { + return new TwigFilter($name, static fn () => ''); + } + $e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); + + throw $e; + } + + if ($filter->isDeprecated()) { + $src = $this->stream->getSourceContext(); + $filter->triggerDeprecation($src->getPath() ?: $src->getName(), $line); + } + + return $filter; + } + + public function getTest(int $line): TwigTest + { + $name = $this->stream->expect(Token::NAME_TYPE)->getValue(); + + if ($this->stream->test(Token::NAME_TYPE)) { + // try 2-words tests + $name = $name.' '.$this->getCurrentToken()->getValue(); + + try { + $test = $this->env->getTest($name); + } catch (SyntaxError $e) { + if (!$this->shouldIgnoreUnknownTwigCallables()) { + throw $e; + } + + $test = null; + } + $this->stream->next(); + } else { + try { + $test = $this->env->getTest($name); + } catch (SyntaxError $e) { + if (!$this->shouldIgnoreUnknownTwigCallables()) { + throw $e; + } + + $test = null; + } + } + + if (!$test) { + if ($this->shouldIgnoreUnknownTwigCallables()) { + return new TwigTest($name, static fn () => ''); + } + $e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $this->stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + if ($test->isDeprecated()) { + $src = $this->stream->getSourceContext(); + $test->triggerDeprecation($src->getPath() ?: $src->getName(), $this->stream->getCurrent()->getLine()); + } + + return $test; + } + + private function cleanupBodyForChildTemplates(Node $body): Node + { + if ($body instanceof BlockReferenceNode) { + return new EmptyNode(); + } + if ($body instanceof TextNode && $body->isBlank()) { + return new EmptyNode(); + } + + foreach ($body as $k => $node) { + if ($node instanceof BlockReferenceNode) { + // as it has a parent, the block reference won't be used + $body->removeNode($k); + } elseif ($node instanceof TextNode && $node->isBlank()) { + // remove nodes considered as "empty" + $body->removeNode($k); + } + } + + return $body; + } + + private function checkPrecedenceDeprecations(ExpressionParserInterface $expressionParser, AbstractExpression $expr): void + { + $this->expressionRefs[$expr] = $expressionParser; + $precedenceChanges = $this->parsers->getPrecedenceChanges(); + + // Check that the all nodes that are between the 2 precedences have explicit parentheses + if (!isset($precedenceChanges[$expressionParser])) { + return; + } + + if ($expr->hasExplicitParentheses()) { + return; + } + + if ($expressionParser instanceof PrefixExpressionParserInterface) { + /** @var AbstractExpression $node */ + $node = $expr->getNode('node'); + foreach ($precedenceChanges as $ep => $changes) { + if (!\in_array($expressionParser, $changes, true)) { + continue; + } + if (isset($this->expressionRefs[$node]) && $ep === $this->expressionRefs[$node]) { + $change = $expressionParser->getPrecedenceChange(); + trigger_deprecation($change->getPackage(), $change->getVersion(), \sprintf('As the "%s" %s operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in "%s" at line %d.', $expressionParser->getName(), ExpressionParserType::getType($expressionParser)->value, $this->getStream()->getSourceContext()->getName(), $node->getTemplateLine())); } } - - throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); } - // bypass nodes that "capture" the output - if ($node instanceof NodeCaptureInterface) { - // a "block" tag in such a node will serve as a block definition AND be displayed in place as well - return $node; - } - - // "block" tags that are not captured (see above) are only used for defining - // the content of the block. In such a case, nesting it does not work as - // expected as the definition is not part of the default template code flow. - if ($nested && $node instanceof BlockReferenceNode) { - throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext()); - } - - if ($node instanceof NodeOutputInterface) { - return null; - } - - // here, $nested means "being at the root level of a child template" - // we need to discard the wrapping "Node" for the "body" node - $nested = $nested || Node::class !== \get_class($node); - foreach ($node as $k => $n) { - if (null !== $n && null === $this->filterBodyNodes($n, $nested)) { - $node->removeNode($k); + foreach ($precedenceChanges[$expressionParser] as $ep) { + foreach ($expr as $node) { + /** @var AbstractExpression $node */ + if (isset($this->expressionRefs[$node]) && $ep === $this->expressionRefs[$node] && !$node->hasExplicitParentheses()) { + $change = $ep->getPrecedenceChange(); + trigger_deprecation($change->getPackage(), $change->getVersion(), \sprintf('As the "%s" %s operator will change its precedence in the next major version, add explicit parentheses to avoid behavior change in "%s" at line %d.', $ep->getName(), ExpressionParserType::getType($ep)->value, $this->getStream()->getSourceContext()->getName(), $node->getTemplateLine())); + } } } - - return $node; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php index 267718c1f..06bf4b888 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -31,11 +31,16 @@ abstract class BaseDumper abstract protected function formatTime(Profile $profile, $percent): string; + protected function formatRoot(Profile $profile): string + { + return $profile->getName(); + } + private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string { if ($profile->isRoot()) { $this->root = $profile->getDuration(); - $start = $profile->getName(); + $start = $this->formatRoot($profile); } else { if ($profile->isTemplate()) { $start = $this->formatTemplate($profile, $prefix); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php index bb3fbb52a..7cfae16f1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -40,7 +40,7 @@ EOF; return $str; } - private function dumpChildren(string $parent, Profile $profile, &$data) + private function dumpChildren(string $parent, Profile $profile, &$data): void { foreach ($profile as $p) { if ($p->isTemplate()) { @@ -53,7 +53,7 @@ EOF; } } - private function dumpProfile(string $edge, Profile $profile, &$data) + private function dumpProfile(string $edge, Profile $profile, &$data): void { if (isset($data[$edge])) { ++$data[$edge]['ct']; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php index cdab2de59..1bfd81b0e 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -30,18 +30,28 @@ final class HtmlDumper extends BaseDumper return '
'.parent::dump($profile).'
'; } + protected function formatRoot(Profile $profile): string + { + return self::escape($profile->getName()); + } + protected function formatTemplate(Profile $profile, $prefix): string { - return \sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + return \sprintf('%s└ %s', $prefix, self::$colors['template'], self::escape($profile->getTemplate())); } protected function formatNonTemplate(Profile $profile, $prefix): string { - return \sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName()); + return \sprintf('%s└ %s::%s(%s)', $prefix, self::escape($profile->getTemplate()), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', self::escape($profile->getName())); } protected function formatTime(Profile $profile, $percent): string { return \sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); } + + private static function escape(string $value): string + { + return htmlspecialchars($value, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php index 1458bc5fc..4c5c2005d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -17,6 +17,7 @@ use Twig\Node\BodyNode; use Twig\Node\MacroNode; use Twig\Node\ModuleNode; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\Profiler\Node\EnterProfileNode; use Twig\Profiler\Node\LeaveProfileNode; @@ -43,8 +44,8 @@ final class ProfilerNodeVisitor implements NodeVisitorInterface public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ModuleNode) { - $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')])); - $node->setNode('display_end', new Node([new LeaveProfileNode($this->varName), $node->getNode('display_end')])); + $node->setNode('display_start', new Nodes([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')])); + $node->setNode('display_end', new Nodes([new LeaveProfileNode($this->varName), $node->getNode('display_end')])); } elseif ($node instanceof BlockNode) { $node->setNode('body', new BodyNode([ new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName), diff --git a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php index 2928e1646..63861d653 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Profiler/Profile.php @@ -99,6 +99,22 @@ final class Profile implements \IteratorAggregate, \Serializable return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; } + /** + * Returns the start time in microseconds. + */ + public function getStartTime(): float + { + return $this->starts['wt'] ?? 0.0; + } + + /** + * Returns the end time in microseconds. + */ + public function getEndTime(): float + { + return $this->ends['wt'] ?? 0.0; + } + /** * Returns the memory usage in bytes. */ @@ -157,7 +173,7 @@ final class Profile implements \IteratorAggregate, \Serializable public function unserialize($data): void { - $this->__unserialize(unserialize($data)); + $this->__unserialize(unserialize($data, ['allowed_classes' => [self::class]])); } /** diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php index 6e2fcfb07..df143d9e9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/core.php @@ -11,6 +11,9 @@ use Twig\Environment; use Twig\Extension\CoreExtension; +use Twig\Extension\SandboxExtension; +use Twig\Source; +use Twig\Template; /** * @internal @@ -237,7 +240,7 @@ function twig_sort_filter(Environment $env, $array, $arrow = null) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::sort($env, $array, $arrow); + return CoreExtension::sort($env, twig_resolve_is_sandboxed($env), $array, $arrow); } /** @@ -461,11 +464,11 @@ function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) * * @deprecated since Twig 3.9 */ -function twig_array_column($array, $name, $index = null): array +function twig_array_column(Environment $env, $array, $name, $index = null): array { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::column($array, $name, $index); + return CoreExtension::column($env, twig_resolve_is_sandboxed($env), $array, $name, $index); } /** @@ -477,7 +480,7 @@ function twig_array_filter(Environment $env, $array, $arrow) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::filter($env, $array, $arrow); + return CoreExtension::filter($env, twig_resolve_is_sandboxed($env), $array, $arrow); } /** @@ -489,7 +492,7 @@ function twig_array_map(Environment $env, $array, $arrow) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::map($env, $array, $arrow); + return CoreExtension::map($env, twig_resolve_is_sandboxed($env), $array, $arrow); } /** @@ -501,7 +504,7 @@ function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::reduce($env, $array, $arrow, $initial); + return CoreExtension::reduce($env, twig_resolve_is_sandboxed($env), $array, $arrow, $initial); } /** @@ -513,7 +516,7 @@ function twig_array_some(Environment $env, $array, $arrow) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::arraySome($env, $array, $arrow); + return CoreExtension::arraySome($env, $array, $arrow, twig_resolve_is_sandboxed($env)); } /** @@ -525,7 +528,7 @@ function twig_array_every(Environment $env, $array, $arrow) { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::arrayEvery($env, $array, $arrow); + return CoreExtension::arrayEvery($env, $array, $arrow, twig_resolve_is_sandboxed($env)); } /** @@ -533,9 +536,37 @@ function twig_array_every(Environment $env, $array, $arrow) * * @deprecated since Twig 3.9 */ -function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type): void { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); - return CoreExtension::checkArrowInSandbox($env, $arrow, $thing, $type); + CoreExtension::checkArrow(twig_resolve_is_sandboxed($env), $arrow, $thing, $type); +} + +/** + * Recovers the calling Template's Source by walking the PHP backtrace. + * + * @internal + */ +function twig_resolve_caller_source(): ?Source +{ + foreach (debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS) as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Template) { + return $trace['object']->getSourceContext(); + } + } + + return null; +} + +/** + * @internal + */ +function twig_resolve_is_sandboxed(Environment $env): bool +{ + if (!$env->hasExtension(SandboxExtension::class)) { + return false; + } + + return $env->getExtension(SandboxExtension::class)->isSandboxed(twig_resolve_caller_source()); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php index 104b4f4e0..0fd6c7b08 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/debug.php @@ -1,9 +1,9 @@ + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,7 +17,7 @@ use Twig\Extension\DebugExtension; * * @deprecated since Twig 3.9 */ -function twig_var_dump(Environment $env, $context, ...$vars) +function twig_var_dump(Environment $env, $context, ...$vars): void { trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__); diff --git a/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php b/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php index 8f0e6492a..c499e5ec2 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Resources/string_loader.php @@ -1,9 +1,9 @@ + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php b/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php index a3ce17146..67bf75a15 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Runtime/EscaperRuntime.php @@ -17,7 +17,7 @@ use Twig\Markup; final class EscaperRuntime implements RuntimeExtensionInterface { - /** @var array */ + /** @var array */ private $escapers = []; /** @internal */ @@ -37,7 +37,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface * @param string $strategy The strategy name that should be used as a strategy in the escape call * @param callable(string $string, string $charset): string $callable A valid PHP callable */ - public function setEscaper($strategy, callable $callable) + public function setEscaper($strategy, callable $callable): void { $this->escapers[$strategy] = $callable; } @@ -52,7 +52,10 @@ final class EscaperRuntime implements RuntimeExtensionInterface return $this->escapers; } - public function setSafeClasses(array $safeClasses = []) + /** + * @param array, string[]> $safeClasses + */ + public function setSafeClasses(array $safeClasses = []): void { $this->safeClasses = []; $this->safeLookup = []; @@ -61,7 +64,11 @@ final class EscaperRuntime implements RuntimeExtensionInterface } } - public function addSafeClass(string $class, array $strategies) + /** + * @param class-string<\Stringable> $class + * @param string[] $strategies + */ + public function addSafeClass(string $class, array $strategies): void { $class = ltrim($class, '\\'); if (!isset($this->safeClasses[$class])) { @@ -93,7 +100,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface if (!\is_string($string)) { if ($string instanceof \Stringable) { if ($autoescape) { - $c = \get_class($string); + $c = $string::class; if (!isset($this->safeClasses[$c])) { $this->safeClasses[$c] = []; foreach (class_parents($string) + class_implements($string) as $class) { @@ -111,7 +118,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface } $string = (string) $string; - } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'html_attr_relaxed', 'url'], true)) { // we return the input as is (which can be of any type) return $string; } @@ -127,6 +134,10 @@ final class EscaperRuntime implements RuntimeExtensionInterface case 'html': // see https://www.php.net/htmlspecialchars + if ('UTF-8' === $charset) { + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + } + // Using a static variable to avoid initializing the array // each time the function is called. Moving the declaration on the // top of the function slow downs other escaping strategies. @@ -174,7 +185,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); } - $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', static function ($matches) { $char = $matches[0]; /* @@ -182,7 +193,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface * Escape sequences supported only by JavaScript, not JSON, are omitted. * \" is also supported but omitted, because the resulting string is not HTML safe. */ - static $shortMap = [ + $short = match ($char) { '\\' => '\\\\', '/' => '\\/', "\x08" => '\b', @@ -190,10 +201,11 @@ final class EscaperRuntime implements RuntimeExtensionInterface "\x0A" => '\n', "\x0D" => '\r', "\x09" => '\t', - ]; + default => false, + }; - if (isset($shortMap[$char])) { - return $shortMap[$char]; + if ($short) { + return $short; } $codepoint = mb_ord($char, 'UTF-8'); @@ -225,7 +237,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); } - $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', static function ($matches) { $char = $matches[0]; return \sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); @@ -238,6 +250,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface return $string; case 'html_attr': + case 'html_attr_relaxed': if ('UTF-8' !== $charset) { $string = $this->convertEncoding($string, 'UTF-8', $charset); } @@ -246,7 +259,12 @@ final class EscaperRuntime implements RuntimeExtensionInterface throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); } - $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { + $regex = match ($strategy) { + 'html_attr' => '#[^a-zA-Z0-9,\.\-_]#Su', + 'html_attr_relaxed' => '#[^a-zA-Z0-9,\.\-_:@\[\]]#Su', + }; + + $string = preg_replace_callback($regex, static function ($matches) { /** * This function is adapted from code coming from Zend Framework. * @@ -254,7 +272,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface * @license https://framework.zend.com/license/new-bsd New BSD License */ $chr = $matches[0]; - $ord = \ord($chr); + $ord = \ord($chr[0]); /* * The following replaces characters undefined in HTML with the @@ -275,18 +293,13 @@ final class EscaperRuntime implements RuntimeExtensionInterface * entities that XML supports. Using HTML entities would result in this error: * XML Parsing Error: undefined entity */ - static $entityMap = [ + return match ($ord) { 34 => '"', /* quotation mark */ 38 => '&', /* ampersand */ 60 => '<', /* less-than sign */ 62 => '>', /* greater-than sign */ - ]; - - if (isset($entityMap[$ord])) { - return $entityMap[$ord]; - } - - return \sprintf('&#x%02X;', $ord); + default => \sprintf('&#x%02X;', $ord), + }; } /* @@ -310,7 +323,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface return $this->escapers[$strategy]($string, $charset); } - $validStrategies = implode('", "', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($this->escapers))); + $validStrategies = implode('", "', array_merge(['html', 'js', 'url', 'css', 'html_attr', 'html_attr_relaxed'], array_keys($this->escapers))); throw new RuntimeError(\sprintf('Invalid escaping strategy "%s" (valid ones: "%s").', $strategy, $validStrategies)); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Runtime/SandboxBridgeRuntime.php b/data/web/inc/lib/vendor/twig/twig/src/Runtime/SandboxBridgeRuntime.php new file mode 100644 index 000000000..0e83e3d75 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Runtime/SandboxBridgeRuntime.php @@ -0,0 +1,31 @@ + $context + */ + public function render(string $name, array $context): string + { + return $this->sandbox->render($name, $context); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/Sandbox.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/Sandbox.php new file mode 100644 index 000000000..8b15d753c --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/Sandbox.php @@ -0,0 +1,81 @@ + + */ +final class Sandbox implements SandboxInterface +{ + public function __construct( + private Environment $env, + SecurityPolicyInterface $policy, + ) { + if ($policy instanceof SecurityPolicy && !$policy->isStrict()) { + throw new \LogicException('The sandbox requires a strict security policy, call "setStrict(true)" on a dedicated policy for this sandbox.'); + } + + try { + $env->addExtension(new SandboxExtension($policy, true)); + } catch (\LogicException $e) { + throw new \LogicException(\sprintf('The environment passed to "%s" must be dedicated to it: pass a freshly built environment that has no "%s" registered and has not been used yet.', self::class, SandboxExtension::class), 0, $e); + } + } + + public function render(string $name, array $context = []): string + { + return $this->env->render($name, $context); + } + + public function display(string $name, array $context = []): void + { + $this->env->display($name, $context); + } + + public function stream(string $name, array $context = []): iterable + { + yield from $this->env->load($name)->stream($context); + } + + public function renderBlock(string $name, string $block, array $context = []): string + { + return $this->env->load($name)->renderBlock($block, $context); + } + + public function displayBlock(string $name, string $block, array $context = []): void + { + $this->env->load($name)->displayBlock($block, $context); + } + + public function streamBlock(string $name, string $block, array $context = []): iterable + { + yield from $this->env->load($name)->streamBlock($block, $context); + } + + public function createTemplate(string $template, ?string $name = null): TemplateWrapper + { + return $this->env->createTemplate($template, $name); + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SandboxInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SandboxInterface.php new file mode 100644 index 000000000..54897dfe4 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SandboxInterface.php @@ -0,0 +1,63 @@ + + */ +interface SandboxInterface +{ + /** + * @param array $context + */ + public function render(string $name, array $context = []): string; + + /** + * @param array $context + */ + public function display(string $name, array $context = []): void; + + /** + * @param array $context + * + * @return iterable + */ + public function stream(string $name, array $context = []): iterable; + + /** + * @param array $context + */ + public function renderBlock(string $name, string $block, array $context = []): string; + + /** + * @param array $context + */ + public function displayBlock(string $name, string $block, array $context = []): void; + + /** + * @param array $context + * + * @return iterable + */ + public function streamBlock(string $name, string $block, array $context = []): iterable; + + /** + * Creates a sandboxed template from source. + * + * The template can only reference templates available in the environment loader. + */ + public function createTemplate(string $template, ?string $name = null): TemplateWrapper; +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityChecker.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityChecker.php new file mode 100644 index 000000000..5d1cd5356 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityChecker.php @@ -0,0 +1,244 @@ + + * + * @internal + */ +final class SecurityChecker +{ + private bool $sandboxed = false; + + public function __construct( + private SecurityPolicyInterface $policy, + private bool $sandboxedGlobally = false, + private ?SourcePolicyInterface $sourcePolicy = null, + ) { + } + + /** + * Toggles the sandbox mode for the legacy stateful mechanisms. + * + * To be removed in 4.0 together with the $sandboxed state: a sandbox + * environment is permanently sandboxed. + */ + public function setSandboxed(bool $sandboxed): void + { + $this->sandboxed = $sandboxed; + } + + public function isSandboxed(?Source $source = null): bool + { + return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source); + } + + /** + * To be removed in 4.0: only the deprecated SandboxExtension::isSandboxedGlobally() uses it. + */ + public function isSandboxedGlobally(): bool + { + return $this->sandboxedGlobally; + } + + /** + * To be removed in 4.0 together with the deprecated SourcePolicyInterface. + */ + private function isSourceSandboxed(?Source $source): bool + { + if (null === $source || null === $this->sourcePolicy) { + return false; + } + + return $this->sourcePolicy->enableSandbox($source); + } + + /** + * To be removed in 4.0: a sandbox policy is fixed at construction. + */ + public function setSecurityPolicy(SecurityPolicyInterface $policy): void + { + $this->policy = $policy; + } + + public function getSecurityPolicy(): SecurityPolicyInterface + { + return $this->policy; + } + + /** + * $source can only be null when called through the deprecated + * SandboxExtension::checkSecurity() method; type it Source in 4.0. + */ + public function checkSecurity(array $tags, array $filters, array $functions, array $tests, ?Source $source): void + { + if (!$this->isSandboxed($source)) { + return; + } + + try { + if ((new \ReflectionMethod($this->policy, 'checkSecurity'))->getNumberOfParameters() >= 4) { + $this->policy->checkSecurity($tags, $filters, $functions, $tests); + + return; + } + + trigger_deprecation('twig/twig', '3.28', 'The "%s::checkSecurity()" method will take a 4th "array $tests" argument in 4.0; not declaring it is deprecated.', $this->policy::class); + + $this->policy->checkSecurity($tags, $filters, $functions); + } catch (SecurityError $e) { + $e->setSourceContext($source); + + throw $e; + } + } + + public function checkMethodAllowed(mixed $obj, mixed $method, int $lineno = -1, ?Source $source = null): void + { + if ($this->isSandboxed($source)) { + try { + $this->policy->checkMethodAllowed($obj, $method); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function checkPropertyAllowed(mixed $obj, mixed $property, int $lineno = -1, ?Source $source = null): void + { + if ($this->isSandboxed($source)) { + try { + $this->policy->checkPropertyAllowed($obj, $property); + } catch (SecurityNotAllowedPropertyError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + /** + * @throws SecurityNotAllowedMethodError + */ + public function ensureToStringAllowed(mixed $obj, int $lineno = -1, ?Source $source = null): mixed + { + return $this->doEnsureToStringAllowed($obj, $lineno, $source, new \SplObjectStorage()); + } + + /** + * Materializes a spread operand and runs the policy on every element. + * + * @throws SecurityNotAllowedMethodError + */ + public function ensureSpreadAllowed(iterable $obj, int $lineno = -1, ?Source $source = null): array + { + $seen = new \SplObjectStorage(); + if ($obj instanceof \Traversable) { + $seen[$obj] = true; + $obj = iterator_to_array($obj); + } + + $this->ensureToStringAllowedForArray($obj, $lineno, $source, $seen); + + return $obj; + } + + private function doEnsureToStringAllowed(mixed $obj, int $lineno, ?Source $source, \SplObjectStorage $seen): mixed + { + if (\is_array($obj)) { + $this->ensureToStringAllowedForArray($obj, $lineno, $source, $seen); + + return $obj; + } + + if (!$this->isSandboxed($source)) { + return $obj; + } + + if ($obj instanceof \Stringable) { + try { + $this->policy->checkMethodAllowed($obj, '__toString'); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + + // Elements yielded by a Traversable may be string-coerced downstream + // (e.g. by `join`/`replace`), bypassing the policy. Check them now. + if ($obj instanceof \Traversable) { + if (isset($seen[$obj])) { + return $obj; + } + $seen[$obj] = true; + + // IteratorAggregate::getIterator() is idempotent, so we can walk + // the elements and return the original object: host code typed + // against a specific class (e.g. FormView) keeps working. + if ($obj instanceof \IteratorAggregate) { + foreach ($obj as $v) { + $this->doEnsureToStringAllowed($v, $lineno, $source, $seen); + } + + return $obj; + } + + // Single-pass Iterator/Generator: materialize to validate. + $array = iterator_to_array($obj); + $this->ensureToStringAllowedForArray($array, $lineno, $source, $seen); + + if (!$obj instanceof \Stringable) { + return $array; + } + } + + return $obj; + } + + private function ensureToStringAllowedForArray(array $obj, int $lineno, ?Source $source, \SplObjectStorage $seen, array &$stack = []): void + { + foreach ($obj as $k => $v) { + if (!$v) { + continue; + } + + if (!\is_array($v)) { + $this->doEnsureToStringAllowed($v, $lineno, $source, $seen); + continue; + } + + if ($r = \ReflectionReference::fromArrayElement($obj, $k)) { + if (isset($stack[$r->getId()])) { + continue; + } + + $stack[$r->getId()] = true; + } + + $this->ensureToStringAllowedForArray($v, $lineno, $source, $seen, $stack); + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php index 02d306360..9293a3f0b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php @@ -18,7 +18,7 @@ namespace Twig\Sandbox; */ final class SecurityNotAllowedFilterError extends SecurityError { - private $filterName; + private string $filterName; public function __construct(string $message, string $functionName) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php index 4f76dc6ec..71c9f02bc 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php @@ -18,7 +18,7 @@ namespace Twig\Sandbox; */ final class SecurityNotAllowedFunctionError extends SecurityError { - private $functionName; + private string $functionName; public function __construct(string $message, string $functionName) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php index 8df9d0baa..98e8e434d 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php @@ -18,8 +18,8 @@ namespace Twig\Sandbox; */ final class SecurityNotAllowedMethodError extends SecurityError { - private $className; - private $methodName; + private string $className; + private string $methodName; public function __construct(string $message, string $className, string $methodName) { @@ -33,7 +33,7 @@ final class SecurityNotAllowedMethodError extends SecurityError return $this->className; } - public function getMethodName() + public function getMethodName(): string { return $this->methodName; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php index 42ec4f386..e74ffeddb 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php @@ -18,8 +18,8 @@ namespace Twig\Sandbox; */ final class SecurityNotAllowedPropertyError extends SecurityError { - private $className; - private $propertyName; + private string $className; + private string $propertyName; public function __construct(string $message, string $className, string $propertyName) { @@ -33,7 +33,7 @@ final class SecurityNotAllowedPropertyError extends SecurityError return $this->className; } - public function getPropertyName() + public function getPropertyName(): string { return $this->propertyName; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php index 4522150e1..f9cd625b4 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php @@ -18,7 +18,7 @@ namespace Twig\Sandbox; */ final class SecurityNotAllowedTagError extends SecurityError { - private $tagName; + private string $tagName; public function __construct(string $message, string $tagName) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTestError.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTestError.php new file mode 100644 index 000000000..af702ba68 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTestError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedTestError extends SecurityError +{ + private string $testName; + + public function __construct(string $message, string $testName) + { + parent::__construct($message); + $this->testName = $testName; + } + + public function getTestName(): string + { + return $this->testName; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php index 988e37216..f44bd3585 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicy.php @@ -26,14 +26,17 @@ final class SecurityPolicy implements SecurityPolicyInterface private $allowedMethods; private $allowedProperties; private $allowedFunctions; + private array $allowedTests; + private bool $strict = false; - public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = [], array $allowedTests = []) { $this->allowedTags = $allowedTags; $this->allowedFilters = $allowedFilters; $this->setAllowedMethods($allowedMethods); $this->allowedProperties = $allowedProperties; $this->allowedFunctions = $allowedFunctions; + $this->allowedTests = $allowedTests; } public function setAllowedTags(array $tags): void @@ -50,7 +53,7 @@ final class SecurityPolicy implements SecurityPolicyInterface { $this->allowedMethods = []; foreach ($methods as $class => $m) { - $this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]); + $this->allowedMethods[$class] = array_map('strtolower', \is_array($m) ? $m : [$m]); } } @@ -64,14 +67,46 @@ final class SecurityPolicy implements SecurityPolicyInterface $this->allowedFunctions = $functions; } - public function checkSecurity($tags, $filters, $functions): void + public function setAllowedTests(array $tests): void { + $this->allowedTests = $tests; + } + + /** + * Toggles strict mode. + * + * In strict mode, the tags, functions, and tests that are historically always + * allowed in a sandbox (the ``extends`` and ``use`` tags, the ``parent``, + * ``block``, and ``attribute`` functions, and any test) are no longer implicitly + * allowed and must be added to the relevant allow-list to be usable. Use this + * flag in 3.x to opt-in to the forthcoming 4.0 behavior and silence the related + * deprecations. + */ + public function setStrict(bool $strict): void + { + $this->strict = $strict; + } + + /** + * @internal + */ + public function isStrict(): bool + { + return $this->strict; + } + + public function checkSecurity($tags, $filters, $functions, array $tests = []): void + { + if (\func_num_args() < 4) { + trigger_deprecation('twig/twig', '3.28', 'Not passing the "$tests" argument to "%s::checkSecurity()" is deprecated; it will be required in 4.0.', static::class); + } + foreach ($tags as $tag) { - if (!\in_array($tag, $this->allowedTags)) { - if ('extends' === $tag) { - trigger_deprecation('twig/twig', '3.12', 'The "extends" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.'); - } elseif ('use' === $tag) { - trigger_deprecation('twig/twig', '3.12', 'The "use" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed.'); + if (!\in_array($tag, $this->allowedTags, true)) { + if (!$this->strict && 'extends' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "extends" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); + } elseif (!$this->strict && 'use' === $tag) { + trigger_deprecation('twig/twig', '3.12', 'The "use" tag is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); } else { throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); } @@ -79,14 +114,32 @@ final class SecurityPolicy implements SecurityPolicyInterface } foreach ($filters as $filter) { - if (!\in_array($filter, $this->allowedFilters)) { + if (!\in_array($filter, $this->allowedFilters, true)) { throw new SecurityNotAllowedFilterError(\sprintf('Filter "%s" is not allowed.', $filter), $filter); } } foreach ($functions as $function) { - if (!\in_array($function, $this->allowedFunctions)) { - throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function); + if (!\in_array($function, $this->allowedFunctions, true)) { + if (!$this->strict && 'parent' === $function) { + trigger_deprecation('twig/twig', '3.27', 'The "parent" function is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); + } elseif (!$this->strict && 'block' === $function) { + trigger_deprecation('twig/twig', '3.27', 'The "block" function is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); + } elseif (!$this->strict && 'attribute' === $function) { + trigger_deprecation('twig/twig', '3.27', 'The "attribute" function is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); + } else { + throw new SecurityNotAllowedFunctionError(\sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + foreach ($tests as $test) { + if (!\in_array($test, $this->allowedTests, true)) { + if (!$this->strict) { + trigger_deprecation('twig/twig', '3.28', 'The "%s" test is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).', $test); + } else { + throw new SecurityNotAllowedTestError(\sprintf('Test "%s" is not allowed.', $test), $test); + } } } } @@ -98,16 +151,16 @@ final class SecurityPolicy implements SecurityPolicyInterface } $allowed = false; - $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + $method = strtolower($method); foreach ($this->allowedMethods as $class => $methods) { - if ($obj instanceof $class && \in_array($method, $methods)) { + if ($obj instanceof $class && \in_array($method, $methods, true)) { $allowed = true; break; } } if (!$allowed) { - $class = \get_class($obj); + $class = $obj::class; throw new SecurityNotAllowedMethodError(\sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); } } @@ -116,14 +169,14 @@ final class SecurityPolicy implements SecurityPolicyInterface { $allowed = false; foreach ($this->allowedProperties as $class => $properties) { - if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties])) { + if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties], true)) { $allowed = true; break; } } if (!$allowed) { - $class = \get_class($obj); + $class = $obj::class; throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php index 36471c54c..cb36ba5be 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php @@ -22,10 +22,11 @@ interface SecurityPolicyInterface * @param string[] $tags * @param string[] $filters * @param string[] $functions + * @param string[] $tests * * @throws SecurityError */ - public function checkSecurity($tags, $filters, $functions): void; + public function checkSecurity($tags, $filters, $functions/* , array $tests */): void; /** * @param object $obj diff --git a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php index b952f1ea6..1b2960855 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Sandbox/SourcePolicyInterface.php @@ -17,6 +17,8 @@ use Twig\Source; * Interface for a class that can optionally enable the sandbox mode based on a template's Twig\Source. * * @author Yaakov Saxon + * + * @deprecated since Twig 3.27.0 with no replacement */ interface SourcePolicyInterface { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Source.php b/data/web/inc/lib/vendor/twig/twig/src/Source.php index 0f626b62d..0c97f255a 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Source.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Source.php @@ -44,4 +44,23 @@ final class Source { return $this->path; } + + /** + * Returns the 1-based column for a 0-based byte offset in the source code. + * + * A negative offset means the position is unknown and yields null. + * + * @return positive-int|null + */ + public function getColumn(int $offset): ?int + { + if ($offset < 0) { + return null; + } + + $before = str_replace(["\r\n", "\r"], "\n", substr($this->code, 0, $offset)); + $lineStart = strrpos($before, "\n"); + + return false === $lineStart ? \strlen($before) + 1 : \strlen($before) - $lineStart; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Template.php b/data/web/inc/lib/vendor/twig/twig/src/Template.php index 7b3ce8161..f24b2b768 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Template.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Template.php @@ -13,7 +13,6 @@ namespace Twig; use Twig\Error\Error; -use Twig\Error\LoaderError; use Twig\Error\RuntimeError; /** @@ -37,10 +36,12 @@ abstract class Template protected $parents = []; protected $blocks = []; protected $traits = []; + protected $traitAliases = []; protected $extensions = []; protected $sandbox; private $useYield; + private ?MacroNamespace $macroNamespace = null; public function __construct( protected Environment $env, @@ -66,37 +67,61 @@ abstract class Template */ abstract public function getSourceContext(): Source; + /** + * Returns the escaping strategy the template body was compiled with. + * + * This describes the template's own source, not its output: `autoescape`, + * `escape`, and anything rendered by a parent, embedded, or included + * template can use another strategy. + * + * @return string|false The strategy name or false when the template is not autoescaped + */ + public function getDefaultEscapeStrategy(): string|false + { + return false; + } + /** * Returns the parent template. * * This method is for internal use only and should never be called * directly. * - * @return self|TemplateWrapper|false The parent template or false if there is no parent + * @return self|false The parent template or false if there is no parent */ - public function getParent(array $context): self|TemplateWrapper|false + public function getParent(array $context): self|false { if (null !== $this->parent) { return $this->parent; } + // The compiled doGetParent() may evaluate user expressions (filters, + // functions, method calls) when the parent name is dynamic. Make sure + // the sandbox security check runs first so those expressions cannot + // bypass the allow-list when getParent() is reached before the first + // ensureSecurityChecked() call on this template (e.g. via a macro call + // resolved against a parent, or yieldBlock() into a pre-warmed instance). + $this->ensureSecurityChecked(); + try { - if (!$parent = $this->doGetParent($context)) { - return false; - } + $parent = $this->doGetParent($context); + } catch (\Throwable $e) { + $this->handleException($e); + } - if ($parent instanceof self || $parent instanceof TemplateWrapper) { - return $this->parents[$parent->getSourceContext()->getName()] = $parent; - } + if (!$parent) { + return false; + } - if (!isset($this->parents[$parent])) { - $this->parents[$parent] = $this->loadTemplate($parent); - } - } catch (LoaderError $e) { - $e->setSourceContext(null); - $e->guess(); + if ($parent instanceof TemplateWrapper) { + $parent = $this->load($parent, -1); + } + if ($parent instanceof self) { + return $this->parents[$parent->getSourceContext()->getName()] = $parent; + } - throw $e; + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->load($parent, -1); } return $this->parents[$parent]; @@ -162,12 +187,21 @@ abstract class Template public function renderParentBlock($name, array $context, array $blocks = []): string { if (!$this->useYield) { + $level = ob_get_level(); if ($this->env->isDebug()) { ob_start(); } else { - ob_start(function () { return ''; }); + ob_start(static function () { return ''; }); + } + try { + $this->displayParentBlock($name, $context, $blocks); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; } - $this->displayParentBlock($name, $context, $blocks); return ob_get_clean(); } @@ -200,7 +234,7 @@ abstract class Template if ($this->env->isDebug()) { ob_start(); } else { - ob_start(function () { return ''; }); + ob_start(static function () { return ''; }); } try { $this->displayBlock($name, $context, $blocks, $useBlocks); @@ -277,21 +311,15 @@ abstract class Template /** * @param string|TemplateWrapper|array $template */ - protected function loadTemplate($template, $templateName = null, $line = null, $index = null): self|TemplateWrapper + protected function load(string|TemplateWrapper|array $template, int $line, ?int $index = null): self { try { if (\is_array($template)) { - return $this->env->resolveTemplate($template); + return $this->env->resolveTemplate($template)->unwrap($this->env); } if ($template instanceof TemplateWrapper) { - return $template; - } - - if ($template instanceof self) { - trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__); - - return $template; + return $template->unwrap($this->env); } if ($template === $this->getTemplateName()) { @@ -306,14 +334,14 @@ abstract class Template return $this->env->loadTemplate($class, $template, $index); } catch (Error $e) { if (!$e->getSourceContext()) { - $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); + $e->setSourceContext($this->getSourceContext()); } if ($e->getTemplateLine() > 0) { throw $e; } - if (!$line) { + if (-1 === $line) { $e->guess(); } else { $e->setTemplateLine($line); @@ -323,14 +351,53 @@ abstract class Template } } + /** + * @param string|TemplateWrapper|array $template + * + * @deprecated since Twig 3.21 and will be removed in 4.0. Use Template::load() instead. + */ + protected function loadTemplate($template, $templateName = null, ?int $line = null, ?int $index = null): self|TemplateWrapper + { + trigger_deprecation('twig/twig', '3.21', 'The "%s" method is deprecated.', __METHOD__); + + if (null === $line) { + $line = -1; + } + + if ($template instanceof self) { + return $template; + } + + return $this->load($template, $line, $index); + } + /** * @internal + * + * @return $this */ public function unwrap(): self { return $this; } + /** + * @internal + */ + public function isOwnedBy(Environment $env): bool + { + return $this->env === $env; + } + + /** + * Returns whether getParent() has stopped depending on the context, which + * only ever happens for a template with no parent or with a constant one. + */ + public function hasFixedParent(): bool + { + return null !== $this->parent; + } + /** * Returns all blocks. * @@ -358,7 +425,7 @@ abstract class Template if ($this->env->isDebug()) { ob_start(); } else { - ob_start(function () { return ''; }); + ob_start(static function () { return ''; }); } try { $this->display($context); @@ -390,24 +457,10 @@ abstract class Template $blocks = array_merge($this->blocks, $blocks); try { + $this->ensureSecurityChecked(); yield from $this->doDisplay($context, $blocks); - } catch (Error $e) { - if (!$e->getSourceContext()) { - $e->setSourceContext($this->getSourceContext()); - } - - // this is mostly useful for \Twig\Error\LoaderError exceptions - // see \Twig\Error\LoaderError - if (-1 === $e->getTemplateLine()) { - $e->guess(); - } - - throw $e; } catch (\Throwable $e) { - $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); - $e->guess(); - - throw $e; + $this->handleException($e); } } @@ -422,6 +475,8 @@ abstract class Template } elseif (isset($this->blocks[$name])) { $template = $this->blocks[$name][0]; $block = $this->blocks[$name][1]; + // expose this template's own blocks so nested block() calls resolve against them when the block is rendered directly (e.g. block(name, template)) + $blocks = array_merge($this->blocks, $blocks); } else { $template = null; $block = null; @@ -434,24 +489,10 @@ abstract class Template if (null !== $template) { try { + $template->ensureSecurityChecked(); yield from $template->$block($context, $blocks); - } catch (Error $e) { - if (!$e->getSourceContext()) { - $e->setSourceContext($template->getSourceContext()); - } - - // this is mostly useful for \Twig\Error\LoaderError exceptions - // see \Twig\Error\LoaderError - if (-1 === $e->getTemplateLine()) { - $e->guess(); - } - - throw $e; } catch (\Throwable $e) { - $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); - $e->guess(); - - throw $e; + $template->handleException($e); } } elseif ($parent = $this->getParent($context)) { yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); @@ -477,7 +518,7 @@ abstract class Template public function yieldParentBlock($name, array $context, array $blocks = []): iterable { if (isset($this->traits[$name])) { - yield from $this->traits[$name][0]->yieldBlock($name, $context, $blocks, false); + yield from $this->traits[$name][0]->yieldBlock($this->traitAliases[$name] ?? $name, $context, $blocks, false); } elseif ($parent = $this->getParent($context)) { yield from $parent->unwrap()->yieldBlock($name, $context, $blocks, false); } else { @@ -485,6 +526,64 @@ abstract class Template } } + /** + * @internal + */ + public function getMacroNamespace(): MacroNamespace + { + return $this->macroNamespace ??= new MacroNamespace($this, $this->loadDeclaredMacros()); + } + + /** + * @return array + */ + protected function loadDeclaredMacros(): array + { + return []; + } + + /** + * Runs the sandbox security check against the current sandbox state. + * + * @internal + */ + public function ensureSecurityChecked(): void + { + } + + /** + * Checks the "use" tag against the sandbox policy. + * + * The constructor resolves "use" traits eagerly, which reaches the loader, + * so that tag alone is checked here; the rest of the policy still runs at + * render time. + * + * @internal + */ + public function ensureTraitsAllowed(): void + { + try { + $this->checkTraitsAllowed(); + } catch (\Throwable $e) { + $this->handleException($e); + } + } + + /** + * @internal + */ + protected function checkTraitsAllowed(): void + { + } + + /** + * @internal + */ + protected function throwUninitializedMacroNamespace(int $line): never + { + throw new RuntimeError(\sprintf('Macros imported in the body of template "%s" are not available because the body was not rendered; move the "import" or "from" tag inside the block or the macro that uses it.', $this->getTemplateName()), $line, $this->getSourceContext()); + } + /** * Auto-generated method to display the template with the given context. * @@ -494,4 +593,23 @@ abstract class Template * @return iterable */ abstract protected function doDisplay(array $context, array $blocks = []): iterable; + + private function handleException(\Throwable $error): never + { + if ($error instanceof Error) { + if (!$error->getSourceContext()) { + $error->setSourceContext($this->getSourceContext()); + } + if (-1 === $error->getTemplateLine()) { + $error->guess(); + } + + throw $error; + } + + $error = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $error->getMessage()), -1, $this->getSourceContext(), $error); + $error->guess(); + + throw $error; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php b/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php index 135c59188..7fbd99765 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TemplateWrapper.php @@ -11,6 +11,8 @@ namespace Twig; +use Twig\Error\RuntimeError; + /** * Exposes a template to userland. * @@ -30,12 +32,28 @@ final class TemplateWrapper ) { } + /** + * @return iterable + */ + public function stream(array $context = []): iterable + { + yield from $this->template->yield($context); + } + + /** + * @return iterable + */ + public function streamBlock(string $name, array $context = []): iterable + { + yield from $this->template->yieldBlock($name, $context + $this->env->getGlobals()); + } + public function render(array $context = []): string { return $this->template->render($context); } - public function display(array $context = []) + public function display(array $context = []): void { // using func_get_args() allows to not expose the blocks argument // as it should only be used by internal code @@ -44,7 +62,7 @@ final class TemplateWrapper public function hasBlock(string $name, array $context = []): bool { - return $this->template->hasBlock($name, $context); + return $this->template->hasBlock($name, $context + $this->env->getGlobals()); } /** @@ -52,7 +70,7 @@ final class TemplateWrapper */ public function getBlockNames(array $context = []): array { - return $this->template->getBlockNames($context); + return $this->template->getBlockNames($context + $this->env->getGlobals()); } public function renderBlock(string $name, array $context = []): string @@ -60,7 +78,7 @@ final class TemplateWrapper return $this->template->renderBlock($name, $context + $this->env->getGlobals()); } - public function displayBlock(string $name, array $context = []) + public function displayBlock(string $name, array $context = []): void { $context += $this->env->getGlobals(); foreach ($this->template->yieldBlock($name, $context) as $data) { @@ -79,12 +97,38 @@ final class TemplateWrapper } /** - * @internal + * Returns the escaping strategy the template body was compiled with. * - * @return Template + * This describes the template's own source, not its output: `autoescape`, + * `escape`, and anything rendered by a parent, embedded, or included + * template can use another strategy. + * + * @return string|false The strategy name or false when the template is not autoescaped */ - public function unwrap() + public function getDefaultEscapeStrategy(): string|false { + return $this->template->getDefaultEscapeStrategy(); + } + + /** + * @internal + */ + public function isOwnedBy(Environment $env): bool + { + return $this->env === $env && $this->template->isOwnedBy($env); + } + + /** + * @internal + */ + public function unwrap(?Environment $env = null): Template + { + if (null === $env) { + trigger_deprecation('twig/twig', '3.30', 'Calling "%s()" without arguments is deprecated, pass the Twig environment instead.', __METHOD__); + } elseif (!$this->isOwnedBy($env)) { + throw new RuntimeError(\sprintf('A "%s" can only be used with the "%s" that created it.', self::class, Environment::class)); + } + return $this->template; } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php b/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php index 8690a809e..1941a7209 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Test/IntegrationTestCase.php @@ -11,12 +11,15 @@ namespace Twig\Test; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Twig\Environment; use Twig\Error\Error; use Twig\Extension\ExtensionInterface; use Twig\Loader\ArrayLoader; use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\TokenParser\TokenParserInterface; use Twig\TwigFilter; use Twig\TwigFunction; use Twig\TwigTest; @@ -85,8 +88,45 @@ abstract class IntegrationTestCase extends TestCase } /** - * @dataProvider getTests + * @return array */ + protected function getUndefinedFilterCallbacks(): array + { + return []; + } + + /** + * @return array + */ + protected function getUndefinedFunctionCallbacks(): array + { + return []; + } + + /** + * @return array + */ + protected function getUndefinedTestCallbacks(): array + { + return []; + } + + /** + * @return array + */ + protected function getUndefinedTokenParserCallbacks(): array + { + return []; + } + + /** + * The annotation feeds PHPUnit < 10; the attribute feeds PHPUnit >= 10 and must point to a static provider, as PHPUnit >= 11 rejects non-static ones. + * + * @dataProvider getTests + * + * @return void + */ + #[DataProvider('provideTests')] public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') { $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); @@ -96,13 +136,28 @@ abstract class IntegrationTestCase extends TestCase * @dataProvider getLegacyTests * * @group legacy + * + * @return void */ + #[DataProvider('provideLegacyTests'), Group('legacy')] public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') { $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); } + final public static function provideTests(): iterable + { + return self::assembleTests(false, static::getFixturesDirectory()); + } + + final public static function provideLegacyTests(): iterable + { + return self::assembleTests(true, static::getFixturesDirectory()); + } + /** + * @return iterable + * * @final since Twig 3.13 */ public function getTests($name, $legacyTests = false) @@ -114,6 +169,11 @@ abstract class IntegrationTestCase extends TestCase $fixturesDir = $this->getFixturesDir(); } + return self::assembleTests($legacyTests, $fixturesDir); + } + + private static function assembleTests(bool $legacyTests, string $fixturesDir): array + { $fixturesDir = realpath($fixturesDir); $tests = []; @@ -146,10 +206,10 @@ abstract class IntegrationTestCase extends TestCase throw new \InvalidArgumentException(\sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); } - $tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; + $tests[str_replace($fixturesDir.'/', '', $file)] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; } - if ($legacyTests && empty($tests)) { + if ($legacyTests && !$tests) { // add a dummy test to avoid a PHPUnit message return [['not', '-', '', [], '', []]]; } @@ -159,16 +219,24 @@ abstract class IntegrationTestCase extends TestCase /** * @final since Twig 3.13 + * + * @return iterable */ public function getLegacyTests() { return $this->getTests('testLegacyIntegration', true); } - protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + /** + * @return void + */ + protected function doIntegrationTest($file, $message, $condition, $templateSources, $exception, $outputs, $deprecation = '') { if (!$outputs) { - $this->markTestSkipped('no tests to run'); + // dummy test added by assembleTests() when there is no (legacy) test to run + $this->expectNotToPerformAssertions(); + + return; } if ($condition) { @@ -185,10 +253,10 @@ abstract class IntegrationTestCase extends TestCase 'strict_variables' => true, ], $match[2] ? eval($match[2].';') : []); // make sure that template are always compiled even if they are the same (useful when testing with more than one data/expect sections) - foreach ($templates as $j => $template) { - $templates[$j] = $template.str_repeat(' ', $i); + foreach ($templateSources as $name => $template) { + $templateSources[$name] = $template.str_repeat(' ', $i); } - $loader = new ArrayLoader($templates); + $loader = new ArrayLoader($templateSources); $twig = new Environment($loader, $config); $twig->addGlobal('global', 'global'); foreach ($this->getRuntimeLoaders() as $runtimeLoader) { @@ -211,9 +279,26 @@ abstract class IntegrationTestCase extends TestCase $twig->addFunction($function); } + foreach ($this->getUndefinedFilterCallbacks() as $callback) { + $twig->registerUndefinedFilterCallback($callback); + } + + foreach ($this->getUndefinedFunctionCallbacks() as $callback) { + $twig->registerUndefinedFunctionCallback($callback); + } + + foreach ($this->getUndefinedTestCallbacks() as $callback) { + $twig->registerUndefinedTestCallback($callback); + } + + foreach ($this->getUndefinedTokenParserCallbacks() as $callback) { + $twig->registerUndefinedTokenParserCallback($callback); + } + $deprecations = []; + $templates = []; try { - $prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { + $prevHandler = set_error_handler(static function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { if (\E_USER_DEPRECATED === $type) { $deprecations[] = $msg; @@ -223,36 +308,55 @@ abstract class IntegrationTestCase extends TestCase return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false; }); - $template = $twig->load('index.twig'); + foreach (array_keys($templateSources) as $templateName) { + $templates[$templateName] = $twig->load($templateName); + } } catch (\Exception $e) { if (false !== $exception) { $message = $e->getMessage(); - $this->assertSame(trim($exception), trim(\sprintf('%s: %s', \get_class($e), $message))); + $this->assertSame(trim($exception), trim(\sprintf('%s: %s', $e::class, $message))); $last = substr($message, \strlen($message) - 1); $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.'); return; } - throw new Error(\sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + throw new Error(\sprintf('%s: %s', $e::class, $e->getMessage()), -1, null, $e); } finally { restore_error_handler(); } - $this->assertSame($deprecation, implode("\n", $deprecations)); + $template = $templates['index.twig']; + $captureRenderDeprecations = '' !== $deprecation; try { + if ($captureRenderDeprecations) { + $prevHandler = set_error_handler(static function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + + return true; + } + + return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false; + }); + } + $output = trim($template->render(eval($match[1].';')), "\n "); } catch (\Exception $e) { if (false !== $exception) { - $this->assertSame(trim($exception), trim(\sprintf('%s: %s', \get_class($e), $e->getMessage()))); + $this->assertStringMatchesFormat(trim($exception), trim(\sprintf('%s: %s', $e::class, $e->getMessage()))); return; } - $e = new Error(\sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + $e = new Error(\sprintf('%s: %s', $e::class, $e->getMessage()), -1, null, $e); - $output = trim(\sprintf('%s: %s', \get_class($e), $e->getMessage())); + $output = trim(\sprintf('%s: %s', $e::class, $e->getMessage())); + } finally { + if ($captureRenderDeprecations) { + restore_error_handler(); + } } if (false !== $exception) { @@ -266,15 +370,20 @@ abstract class IntegrationTestCase extends TestCase if ($expected !== $output) { printf("Compiled templates that failed on case %d:\n", $i + 1); - foreach (array_keys($templates) as $name) { + foreach (array_keys($templateSources) as $name) { echo "Template: $name\n"; echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext($name)))); } } $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + + $this->assertSame($deprecation, implode("\n", $deprecations)); } } + /** + * @return array + */ protected static function parseTemplates($test) { $templates = []; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php b/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php index bac0ea6d0..3ce58b3bf 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Test/NodeTestCase.php @@ -26,6 +26,9 @@ abstract class NodeTestCase extends TestCase */ private $currentEnv; + /** + * @return iterable + */ public function getTests() { return []; @@ -42,15 +45,22 @@ abstract class NodeTestCase extends TestCase } /** + * The non-static "getTests" is intentionally not mirrored as an attribute: PHPUnit >= 11 rejects non-static providers, so PHPUnit >= 10 relies on the static "provideTests" instead. + * * @dataProvider getTests * @dataProvider provideTests + * + * @return void */ - #[DataProvider('getTests'), DataProvider('provideTests')] + #[DataProvider('provideTests')] public function testCompile($node, $source, $environment = null, $isPattern = false) { $this->assertNodeCompilation($source, $node, $environment, $isPattern); } + /** + * @return void + */ public function assertNodeCompilation($source, Node $node, ?Environment $environment = null, $isPattern = false) { $compiler = $this->getCompiler($environment); @@ -63,12 +73,17 @@ abstract class NodeTestCase extends TestCase } } + /** + * @return Compiler + */ protected function getCompiler(?Environment $environment = null) { return new Compiler($environment ?? $this->getEnvironment()); } /** + * @return Environment + * * @final since Twig 3.13 */ protected function getEnvironment() @@ -82,6 +97,8 @@ abstract class NodeTestCase extends TestCase } /** + * @return string + * * @deprecated since Twig 3.13, use createVariableGetter() instead. */ protected function getVariableGetter($name, $line = false) @@ -99,6 +116,8 @@ abstract class NodeTestCase extends TestCase } /** + * @return string + * * @deprecated since Twig 3.13, use createAttributeGetter() instead. */ protected function getAttributeGetter() diff --git a/data/web/inc/lib/vendor/twig/twig/src/Token.php b/data/web/inc/lib/vendor/twig/twig/src/Token.php index 237634ad1..797e6bda1 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Token.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Token.php @@ -30,17 +30,34 @@ final class Token public const PUNCTUATION_TYPE = 9; public const INTERPOLATION_START_TYPE = 10; public const INTERPOLATION_END_TYPE = 11; + /** + * @deprecated since Twig 3.21, "arrow" is now an operator + */ public const ARROW_TYPE = 12; + /** + * @deprecated since Twig 3.21, "spread" is now an operator + */ public const SPREAD_TYPE = 13; + /** + * @param non-negative-int|null $offset + */ public function __construct( private int $type, private $value, private int $lineno, + private ?int $offset = null, + private ?string $documentation = null, ) { + if (self::ARROW_TYPE === $type) { + trigger_deprecation('twig/twig', '3.21', 'The "%s" token type is deprecated, "arrow" is now an operator.', self::ARROW_TYPE); + } + if (self::SPREAD_TYPE === $type) { + trigger_deprecation('twig/twig', '3.21', 'The "%s" token type is deprecated, "spread" is now an operator.', self::SPREAD_TYPE); + } } - public function __toString() + public function __toString(): string { return \sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); } @@ -63,9 +80,46 @@ final class Token $type = self::NAME_TYPE; } - return ($this->type === $type) && ( + if (self::ARROW_TYPE === $type) { + trigger_deprecation('twig/twig', '3.21', 'The "%s" token type is deprecated, "arrow" is now an operator.', self::typeToEnglish(self::ARROW_TYPE)); + + return self::OPERATOR_TYPE === $this->type && '=>' === $this->value; + } + if (self::SPREAD_TYPE === $type) { + trigger_deprecation('twig/twig', '3.21', 'The "%s" token type is deprecated, "spread" is now an operator.', self::typeToEnglish(self::SPREAD_TYPE)); + + return self::OPERATOR_TYPE === $this->type && '...' === $this->value; + } + + $typeMatches = $this->type === $type; + if ($typeMatches && self::PUNCTUATION_TYPE === $type && \in_array($this->value, ['(', '[', '|', '.', '?', '?:'], true) && $values) { + foreach ((array) $values as $value) { + if (\in_array($value, ['(', '[', '|', '.', '?', '?:'], true)) { + trigger_deprecation('twig/twig', '3.21', 'The "%s" token is now an "%s" token instead of a "%s" one.', $this->value, self::typeToEnglish(self::OPERATOR_TYPE), $this->toEnglish()); + + break; + } + } + } + if (!$typeMatches) { + if (self::OPERATOR_TYPE === $type && self::PUNCTUATION_TYPE === $this->type) { + if ($values) { + foreach ((array) $values as $value) { + if (\in_array($value, ['(', '[', '|', '.', '?', '?:'], true)) { + $typeMatches = true; + + break; + } + } + } else { + $typeMatches = true; + } + } + } + + return $typeMatches && ( null === $values - || (\is_array($values) && \in_array($this->value, $values)) + || (\is_array($values) && \in_array($this->value, $values, true)) || $this->value == $values ); } @@ -75,8 +129,31 @@ final class Token return $this->lineno; } + /** + * Returns the 0-based byte offset of the token in the source code. + * + * Returns null for tokens that are not tied to a source position (e.g. + * tokens synthesized by a token parser). + * + * @return non-negative-int|null + */ + public function getOffset(): ?int + { + return $this->offset; + } + + public function getDocumentation(): ?string + { + return $this->documentation; + } + + /** + * @deprecated since Twig 3.19 + */ public function getType(): int { + trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated.', __METHOD__)); + return $this->type; } @@ -85,6 +162,11 @@ final class Token return $this->value; } + public function toEnglish(): string + { + return self::typeToEnglish($this->type); + } + public static function typeToString(int $type, bool $short = false): string { switch ($type) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php index 720ea6762..deae0ec94 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php @@ -11,7 +11,12 @@ namespace Twig\TokenParser; +use Twig\Lexer; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\NodeDocumentation; +use Twig\Node\Nodes; use Twig\Parser; +use Twig\Token; /** * Base class for all token parsers. @@ -29,4 +34,36 @@ abstract class AbstractTokenParser implements TokenParserInterface { $this->parser = $parser; } + + public function isAlwaysAllowedInSandbox(): bool + { + return false; + } + + /** + * Parses an assignment expression like "a, b". + */ + protected function parseAssignmentExpression(): Nodes + { + $stream = $this->parser->getStream(); + $targets = []; + while (true) { + $token = $stream->getCurrent(); + if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + // in this context, string operators are variable names + $stream->next(); + } else { + $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); + } + $target = new AssignContextVariable($token->getValue(), $token->getLine()); + NodeDocumentation::add($target, $token); + $targets[] = $target; + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Nodes($targets); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php index 0a6c1afb5..5b560e749 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php @@ -11,8 +11,10 @@ namespace Twig\TokenParser; -use Twig\Node\Expression\TempNameExpression; +use Twig\ExpressionParser\Infix\FilterExpressionParser; +use Twig\Node\Expression\Variable\LocalVariable; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Node\PrintNode; use Twig\Node\SetNode; use Twig\Token; @@ -31,21 +33,25 @@ final class ApplyTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $lineno = $token->getLine(); - $name = $this->parser->getVarName(); - - $ref = new TempNameExpression($name, $lineno); - $ref->setAttribute('always_defined', true); - - $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref); + $ref = new LocalVariable(null, $lineno); + $filter = $ref; + $op = $this->parser->getEnvironment()->getExpressionParsers()->getByClass(FilterExpressionParser::class); + while (true) { + $filter = $op->parse($this->parser, $filter, $this->parser->getCurrentToken()); + if (!$this->parser->getStream()->test(Token::OPERATOR_TYPE, '|')) { + break; + } + $this->parser->getStream()->next(); + } $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - return new Node([ + return new Nodes([ new SetNode(true, $ref, $body, $lineno), new PrintNode($filter, $lineno), - ], [], $lineno); + ], $lineno); } public function decideApplyEnd(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php index b50b29e65..86feb27e6 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -32,7 +32,7 @@ final class AutoEscapeTokenParser extends AbstractTokenParser if ($stream->test(Token::BLOCK_END_TYPE)) { $value = 'html'; } else { - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = $this->parser->parseExpression(); if (!$expr instanceof ConstantExpression) { throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php index 81d675db0..60ee9b062 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/BlockTokenParser.php @@ -15,7 +15,9 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\BlockNode; use Twig\Node\BlockReferenceNode; +use Twig\Node\EmptyNode; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Node\PrintNode; use Twig\Token; @@ -36,7 +38,8 @@ final class BlockTokenParser extends AbstractTokenParser $lineno = $token->getLine(); $stream = $this->parser->getStream(); $name = $stream->expect(Token::NAME_TYPE)->getValue(); - $this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno)); + $this->parser->setBlock($name, $block = new BlockNode($name, new EmptyNode(), $lineno)); + $this->parser->setDocumentationTarget($block); $this->parser->pushLocalScope(); $this->parser->pushBlockStack($name); @@ -50,8 +53,8 @@ final class BlockTokenParser extends AbstractTokenParser } } } else { - $body = new Node([ - new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), + $body = new Nodes([ + new PrintNode($this->parser->parseExpression(), $lineno), ]); } $stream->expect(Token::BLOCK_END_TYPE); diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php index 164ef26ee..df1ba381f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -33,8 +33,7 @@ final class DeprecatedTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $stream = $this->parser->getStream(); - $expressionParser = $this->parser->getExpressionParser(); - $expr = $expressionParser->parseExpression(); + $expr = $this->parser->parseExpression(); $node = new DeprecatedNode($expr, $token->getLine()); while ($stream->test(Token::NAME_TYPE)) { @@ -44,10 +43,10 @@ final class DeprecatedTokenParser extends AbstractTokenParser switch ($k) { case 'package': - $node->setNode('package', $expressionParser->parseExpression()); + $node->setNode('package', $this->parser->parseExpression()); break; case 'version': - $node->setNode('version', $expressionParser->parseExpression()); + $node->setNode('version', $this->parser->parseExpression()); break; default: throw new SyntaxError(\sprintf('Unknown "%s" option.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext()); diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php index 8afd48559..ca9d03d45 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/DoTokenParser.php @@ -24,7 +24,7 @@ final class DoTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = $this->parser->parseExpression(); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php index 7bf3233e2..fa2791046 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -13,7 +13,7 @@ namespace Twig\TokenParser; use Twig\Node\EmbedNode; use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\Node; use Twig\Token; @@ -28,14 +28,14 @@ final class EmbedTokenParser extends IncludeTokenParser { $stream = $this->parser->getStream(); - $parent = $this->parser->getExpressionParser()->parseExpression(); + $parent = $this->parser->parseExpression(); [$variables, $only, $ignoreMissing] = $this->parseArguments(); $parentToken = $fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine()); if ($parent instanceof ConstantExpression) { $parentToken = new Token(Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); - } elseif ($parent instanceof NameExpression) { + } elseif ($parent instanceof ContextVariable) { $parentToken = new Token(Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php index 86ddfdfba..cba2927f9 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -12,7 +12,7 @@ namespace Twig\TokenParser; -use Twig\Error\SyntaxError; +use Twig\Node\ConfigNode; use Twig\Node\Node; use Twig\Token; @@ -28,18 +28,10 @@ final class ExtendsTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $stream = $this->parser->getStream(); - - if ($this->parser->peekBlockStack()) { - throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext()); - } elseif (!$this->parser->isMainScope()) { - throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); - } - - $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); - + $this->parser->setParent($this->parser->parseExpression(), false); $stream->expect(Token::BLOCK_END_TYPE); - return new Node([], [], $token->getLine()); + return new ConfigNode($token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php index cf655f842..01d07b023 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -12,7 +12,8 @@ namespace Twig\TokenParser; -use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\ForElseNode; use Twig\Node\ForNode; use Twig\Node\Node; use Twig\Token; @@ -34,15 +35,16 @@ final class ForTokenParser extends AbstractTokenParser { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $targets = $this->parseAssignmentExpression(); $stream->expect(Token::OPERATOR_TYPE, 'in'); - $seq = $this->parser->getExpressionParser()->parseExpression(); + $seq = $this->parser->parseExpression(); $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideForFork']); if ('else' == $stream->next()->getValue()) { + $elseLineno = $stream->getCurrent()->getLine(); $stream->expect(Token::BLOCK_END_TYPE); - $else = $this->parser->subparse([$this, 'decideForEnd'], true); + $else = new ForElseNode($this->parser->subparse([$this, 'decideForEnd'], true), $elseLineno); } else { $else = null; } @@ -50,13 +52,11 @@ final class ForTokenParser extends AbstractTokenParser if (\count($targets) > 1) { $keyTarget = $targets->getNode('0'); - $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); $valueTarget = $targets->getNode('1'); } else { - $keyTarget = new AssignNameExpression('_key', $lineno); + $keyTarget = new AssignContextVariable('_key', $lineno); $valueTarget = $targets->getNode('0'); } - $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php index 2ccff5fbe..791072ae7 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -11,7 +11,9 @@ namespace Twig\TokenParser; -use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\ImportNode; use Twig\Node\Node; use Twig\Token; @@ -19,7 +21,7 @@ use Twig\Token; /** * Imports macros. * - * {% from 'forms.html' import forms %} + * {% from 'forms.html.twig' import forms %} * * @internal */ @@ -27,7 +29,7 @@ final class FromTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $macro = $this->parser->getExpressionParser()->parseExpression(); + $macro = $this->parser->parseExpression(); $stream = $this->parser->getStream(); $stream->expect(Token::NAME_TYPE, 'import'); @@ -35,9 +37,10 @@ final class FromTokenParser extends AbstractTokenParser while (true) { $name = $stream->expect(Token::NAME_TYPE)->getValue(); - $alias = $name; if ($stream->nextIf('as')) { - $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + $alias = new AssignContextVariable($stream->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); + } else { + $alias = new AssignContextVariable($name, $token->getLine()); } $targets[$name] = $alias; @@ -49,11 +52,16 @@ final class FromTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); - $node = new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); + $internalRef = new AssignMacroVariable(new MacroVariable(null, $token->getLine()), $this->parser->isMainScope()); + $node = new ImportNode($macro, $internalRef, $token->getLine()); + // Each alias is registered as a "function" imported symbol mapping the local name + // to the macro name and the internal macro variable: a bare call like "my_macro()" + // is syntactically a function call, so FunctionExpressionParser resolves it through + // this symbol into a MacroReferenceExpression. From there, "from" and "import" calls + // share the same runtime path (MacroNamespace::call()). foreach ($targets as $name => $alias) { - $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); + $this->parser->addImportedSymbol('function', $alias->getAttribute('name'), $name, $internalRef); } return $node; diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/GuardTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/GuardTokenParser.php new file mode 100644 index 000000000..eb4886579 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/GuardTokenParser.php @@ -0,0 +1,79 @@ +parser->getStream(); + $typeToken = $stream->expect(Token::NAME_TYPE); + if (!\in_array($typeToken->getValue(), ['function', 'filter', 'test'], true)) { + throw new SyntaxError(\sprintf('Supported guard types are function, filter and test, "%s" given.', $typeToken->getValue()), $typeToken->getLine(), $stream->getSourceContext()); + } + $method = 'get'.$typeToken->getValue(); + + $nameToken = $stream->expect(Token::NAME_TYPE); + $name = $nameToken->getValue(); + if ('test' === $typeToken->getValue() && $stream->test(Token::NAME_TYPE)) { + // try 2-words tests + $name .= ' '.$stream->getCurrent()->getValue(); + $stream->next(); + } + + try { + $exists = null !== $this->parser->getEnvironment()->$method($name); + } catch (SyntaxError) { + $exists = false; + } + + $stream->expect(Token::BLOCK_END_TYPE); + if ($exists) { + $body = $this->parser->subparse([$this, 'decideGuardFork']); + } else { + $body = new EmptyNode(); + $this->parser->subparseIgnoreUnknownTwigCallables([$this, 'decideGuardFork']); + } + $else = new EmptyNode(); + if ('else' === $stream->next()->getValue()) { + $stream->expect(Token::BLOCK_END_TYPE); + $else = $this->parser->subparse([$this, 'decideGuardEnd'], true); + } + $stream->expect(Token::BLOCK_END_TYPE); + + return new Nodes([$exists ? $body : $else]); + } + + public function decideGuardFork(Token $token): bool + { + return $token->test(['else', 'endguard']); + } + + public function decideGuardEnd(Token $token): bool + { + return $token->test(['endguard']); + } + + public function getTag(): string + { + return 'guard'; + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php index 4ea6f3df9..4e3588e5b 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IfTokenParser.php @@ -15,6 +15,7 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\IfNode; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Token; /** @@ -35,7 +36,7 @@ final class IfTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $lineno = $token->getLine(); - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = $this->parser->parseExpression(); $stream = $this->parser->getStream(); $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideIfFork']); @@ -51,7 +52,7 @@ final class IfTokenParser extends AbstractTokenParser break; case 'elseif': - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = $this->parser->parseExpression(); $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideIfFork']); $tests[] = $expr; @@ -69,7 +70,7 @@ final class IfTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - return new IfNode(new Node($tests), $else, $lineno); + return new IfNode(new Nodes($tests), $else, $lineno); } public function decideIfFork(Token $token): bool diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php index f20f35ab3..500d8c391 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -11,7 +11,8 @@ namespace Twig\TokenParser; -use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\ImportNode; use Twig\Node\Node; use Twig\Token; @@ -19,7 +20,7 @@ use Twig\Token; /** * Imports macros. * - * {% import 'forms.html' as forms %} + * {% import 'forms.html.twig' as forms %} * * @internal */ @@ -27,14 +28,14 @@ final class ImportTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $macro = $this->parser->getExpressionParser()->parseExpression(); + $macro = $this->parser->parseExpression(); $this->parser->getStream()->expect(Token::NAME_TYPE, 'as'); - $var = new AssignNameExpression($this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); + $name = $this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(); + $var = new AssignMacroVariable(new MacroVariable($name, $token->getLine()), $this->parser->isMainScope()); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $this->parser->addImportedSymbol('template', $name); - $this->parser->addImportedSymbol('template', $var->getAttribute('name')); - - return new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); + return new ImportNode($macro, $var, $token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php index 466f2288c..55ac1516c 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -12,6 +12,7 @@ namespace Twig\TokenParser; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\IncludeNode; use Twig\Node\Node; use Twig\Token; @@ -19,9 +20,9 @@ use Twig\Token; /** * Includes a template. * - * {% include 'header.html' %} + * {% include 'header.html.twig' %} * Body - * {% include 'footer.html' %} + * {% include 'footer.html.twig' %} * * @internal */ @@ -29,13 +30,16 @@ class IncludeTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = $this->parser->parseExpression(); [$variables, $only, $ignoreMissing] = $this->parseArguments(); return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine()); } + /** + * @return array{0: ?AbstractExpression, 1: bool, 2: bool} + */ protected function parseArguments() { $stream = $this->parser->getStream(); @@ -49,7 +53,7 @@ class IncludeTokenParser extends AbstractTokenParser $variables = null; if ($stream->nextIf(Token::NAME_TYPE, 'with')) { - $variables = $this->parser->getExpressionParser()->parseExpression(); + $variables = $this->parser->parseExpression(); } $only = false; diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php index c7762075c..b5b848432 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/MacroTokenParser.php @@ -13,8 +13,16 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\BodyNode; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\TempNameExpression; +use Twig\Node\Expression\Unary\NegUnary; +use Twig\Node\Expression\Unary\PosUnary; +use Twig\Node\Expression\Variable\LocalVariable; +use Twig\Node\MacroDeclarationNode; use Twig\Node\MacroNode; use Twig\Node\Node; +use Twig\Node\NodeDocumentation; use Twig\Token; /** @@ -33,8 +41,7 @@ final class MacroTokenParser extends AbstractTokenParser $lineno = $token->getLine(); $stream = $this->parser->getStream(); $name = $stream->expect(Token::NAME_TYPE)->getValue(); - - $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + [$arguments, $variadicName] = $this->parseDefinition($name); $stream->expect(Token::BLOCK_END_TYPE); $this->parser->pushLocalScope(); @@ -49,9 +56,10 @@ final class MacroTokenParser extends AbstractTokenParser $this->parser->popLocalScope(); $stream->expect(Token::BLOCK_END_TYPE); - $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno)); + $this->parser->setMacro($name, $macro = new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $variadicName)); + $this->parser->setDocumentationTarget($macro); - return new Node([], [], $lineno); + return new MacroDeclarationNode($name, $lineno); } public function decideBlockEnd(Token $token): bool @@ -63,4 +71,77 @@ final class MacroTokenParser extends AbstractTokenParser { return 'macro'; } + + /** + * @return array{ArrayExpression, string|null} + */ + private function parseDefinition(string $macroName): array + { + $arguments = new ArrayExpression([], $this->parser->getCurrentToken()->getLine()); + $variadicName = null; + $stream = $this->parser->getStream(); + $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { + if (\count($arguments)) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + + // if the comma above was a trailing comma, early exit the argument parse loop + if ($stream->test(Token::PUNCTUATION_TYPE, ')')) { + break; + } + } + + if ($stream->nextIf(Token::OPERATOR_TYPE, '...')) { + $token = $stream->expect(Token::NAME_TYPE, null, 'A variadic argument must be a name'); + $variadicName = (new LocalVariable($token->getValue(), $token->getLine()))->getAttribute('name'); + if (str_starts_with($variadicName, TempNameExpression::RESERVED_NAME_PREFIX)) { + $variadicName = substr($variadicName, \strlen(TempNameExpression::RESERVED_NAME_PREFIX)); + } + if ($stream->test(Token::OPERATOR_TYPE, '=')) { + throw new SyntaxError(\sprintf('The variadic argument "%s" in macro "%s" cannot have a default value.', $variadicName, $macroName), $token->getLine(), $stream->getSourceContext()); + } + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ',') && !$stream->test(Token::PUNCTUATION_TYPE, ')')) { + throw new SyntaxError(\sprintf('The variadic argument "%s" in macro "%s" must be the last one.', $variadicName, $macroName), $token->getLine(), $stream->getSourceContext()); + } + + break; + } + + $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); + $name = new LocalVariable($token->getValue(), $token->getLine()); + NodeDocumentation::add($name, $token); + if ($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) { + $default = $this->parser->parseExpression(); + } else { + $default = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine()); + $default->setAttribute('is_implicit', true); + } + + if (!$this->checkConstantExpression($default)) { + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext()); + } + $arguments->addElement($default, $name); + } + $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return [$arguments, $variadicName]; + } + + // checks that the node only contains "constant" elements + private function checkConstantExpression(Node $node): bool + { + if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression + || $node instanceof NegUnary || $node instanceof PosUnary + )) { + return false; + } + + foreach ($node as $n) { + if (!$this->checkConstantExpression($n)) { + return false; + } + } + + return true; + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php index 70869fbc5..e6f4a63f3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -22,7 +22,7 @@ use Twig\Token; * Marks a section of a template as untrusted code that must be evaluated in the sandbox mode. * * {% sandbox %} - * {% include 'user.html' %} + * {% include 'user.html.twig' %} * {% endsandbox %} * * @see https://twig.symfony.com/doc/api.html#sandbox-extension for details @@ -34,12 +34,16 @@ final class SandboxTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $stream = $this->parser->getStream(); + trigger_deprecation('twig/twig', '3.15', \sprintf('The "sandbox" tag is deprecated in "%s" at line %d.', $stream->getSourceContext()->getName(), $token->getLine())); + $stream->expect(Token::BLOCK_END_TYPE); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); $stream->expect(Token::BLOCK_END_TYPE); // in a sandbox tag, only include tags are allowed - if (!$body instanceof IncludeNode) { + if ($body instanceof IncludeNode) { + $body->setAttribute('sandboxed', true); + } else { foreach ($body as $node) { if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { continue; @@ -48,6 +52,8 @@ final class SandboxTokenParser extends AbstractTokenParser if (!$node instanceof IncludeNode) { throw new SyntaxError('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); } + + $node->setAttribute('sandboxed', true); } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php index bb43907bd..1aabbf582 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/SetTokenParser.php @@ -13,6 +13,7 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Node\SetNode; use Twig\Token; @@ -34,11 +35,11 @@ final class SetTokenParser extends AbstractTokenParser { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $names = $this->parseAssignmentExpression(); $capture = false; if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) { - $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + $values = $this->parseMultitargetExpression(); $stream->expect(Token::BLOCK_END_TYPE); @@ -70,4 +71,17 @@ final class SetTokenParser extends AbstractTokenParser { return 'set'; } + + private function parseMultitargetExpression(): Nodes + { + $targets = []; + while (true) { + $targets[] = $this->parser->parseExpression(); + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Nodes($targets); + } } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TokenParserInterface.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TokenParserInterface.php index bb8db3e5c..ae78714f5 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TokenParserInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TokenParserInterface.php @@ -20,6 +20,8 @@ use Twig\Token; * Interface implemented by token parsers. * * @author Fabien Potencier + * + * @method bool isAlwaysAllowedInSandbox() Whether the tag is always allowed in sandbox mode, even when not explicitly allow-listed. Not implementing this method is deprecated since Twig 3.28, it will be required in 4.0. */ interface TokenParserInterface { diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php index 2e0850e7e..17f86dd49 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/TypesTokenParser.php @@ -13,6 +13,8 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\Node; +use Twig\Node\NodeDocumentation; +use Twig\Node\TypeNode; use Twig\Node\TypesNode; use Twig\Token; use Twig\TokenStream; @@ -20,7 +22,7 @@ use Twig\TokenStream; /** * Declare variable types. * - * {% types {foo: 'int', bar?: 'string'} %} + * {% types {foo: 'number', bar?: 'string'} %} * * @author Jeroen Versteeg * @@ -31,41 +33,47 @@ final class TypesTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $stream = $this->parser->getStream(); - - $types = $this->parseSimpleMappingExpression($stream); - + [$types, $nodes] = $this->parseSimpleMappingExpression($stream); $stream->expect(Token::BLOCK_END_TYPE); - return new TypesNode($types, $token->getLine()); + $node = new TypesNode($types, $token->getLine()); + foreach ($nodes as $name => $type) { + $node->setNode($name, $type); + } + + return $node; } /** - * @return array + * @return array{array, array} * * @throws SyntaxError */ private function parseSimpleMappingExpression(TokenStream $stream): array { - $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); - + $enclosed = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '{'); $types = []; - + $nodes = []; $first = true; - while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + while (!($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE))) { if (!$first) { $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma'); // trailing ,? - if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + if ($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE)) { break; } } $first = false; $nameToken = $stream->expect(Token::NAME_TYPE); - $isOptional = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '?'); - $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)'); + if ($stream->nextIf(Token::OPERATOR_TYPE, '?:')) { + $isOptional = true; + } else { + $isOptional = null !== $stream->nextIf(Token::OPERATOR_TYPE, '?'); + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)'); + } $valueToken = $stream->expect(Token::STRING_TYPE); @@ -73,10 +81,16 @@ final class TypesTokenParser extends AbstractTokenParser 'type' => $valueToken->getValue(), 'optional' => $isOptional, ]; + $node = new TypeNode($nameToken->getValue(), $valueToken->getValue(), $isOptional, $nameToken->getLine()); + NodeDocumentation::add($node, $nameToken); + $nodes[$nameToken->getValue()] = $node; } - $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); - return $types; + if ($enclosed) { + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + } + + return [$types, $nodes]; } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php index 1b96b4047..a85b02308 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/UseTokenParser.php @@ -12,8 +12,10 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; +use Twig\Node\ConfigNode; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Token; /** @@ -34,7 +36,7 @@ final class UseTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $template = $this->parser->getExpressionParser()->parseExpression(); + $template = $this->parser->parseExpression(); $stream = $this->parser->getStream(); if (!$template instanceof ConstantExpression) { @@ -61,9 +63,9 @@ final class UseTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); + $this->parser->addTrait(new Nodes(['template' => $template, 'targets' => new Nodes($targets)])); - return new Node([], [], $token->getLine()); + return new ConfigNode($token->getLine()); } public function getTag(): string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php index 8ce4f02b2..83470d865 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenParser/WithTokenParser.php @@ -31,7 +31,7 @@ final class WithTokenParser extends AbstractTokenParser $variables = null; $only = false; if (!$stream->test(Token::BLOCK_END_TYPE)) { - $variables = $this->parser->getExpressionParser()->parseExpression(); + $variables = $this->parser->parseExpression(); $only = (bool) $stream->nextIf(Token::NAME_TYPE, 'only'); } diff --git a/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php b/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php index c91701bfe..1385ffb89 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TokenStream.php @@ -27,15 +27,19 @@ final class TokenStream private array $tokens, private ?Source $source = null, ) { - $this->source = $source ?: new Source('', ''); + if (null === $this->source) { + trigger_deprecation('twig/twig', '3.16', \sprintf('Not passing a "%s" object to "%s" constructor is deprecated.', Source::class, __CLASS__)); + + $this->source = new Source('', ''); + } } - public function __toString() + public function __toString(): string { return implode("\n", $this->tokens); } - public function injectTokens(array $tokens) + public function injectTokens(array $tokens): void { $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current)); } @@ -72,11 +76,12 @@ final class TokenStream $line = $token->getLine(); throw new SyntaxError(\sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', $message ? $message.'. ' : '', - Token::typeToEnglish($token->getType()), + $token->toEnglish(), $token->getValue() ? \sprintf(' of value "%s"', $token->getValue()) : '', Token::typeToEnglish($type), $value ? \sprintf(' with value "%s"', $value) : ''), $line, - $this->source + $this->source, + columnno: $this->source->getColumn($token->getOffset() ?? -1), ); } $this->next(); @@ -109,7 +114,7 @@ final class TokenStream */ public function isEOF(): bool { - return Token::EOF_TYPE === $this->tokens[$this->current]->getType(); + return $this->tokens[$this->current]->test(Token::EOF_TYPE); } public function getCurrent(): Token @@ -117,11 +122,6 @@ final class TokenStream return $this->tokens[$this->current]; } - /** - * Gets the source associated with this stream. - * - * @internal - */ public function getSourceContext(): Source { return $this->source; diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php b/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php index 2a8ff6116..06b0e2e95 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigCallableInterface.php @@ -13,6 +13,9 @@ namespace Twig; /** * @author Fabien Potencier + * + * @method bool needsIsSandboxed() Whether the callable needs the current sandbox state passed as an argument. Not implementing this method is deprecated since Twig 3.25, it will be required in 4.0. + * @method bool isAlwaysAllowedInSandbox() Whether the callable is always allowed in sandbox mode, even when not explicitly allow-listed. Not implementing this method is deprecated since Twig 3.28, it will be required in 4.0. */ interface TwigCallableInterface extends \Stringable { diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php b/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php index 70b1f8f3f..dece51843 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigFilter.php @@ -54,12 +54,12 @@ final class TwigFilter extends AbstractTwigCallable return $this->options['is_safe_callback']($filterArgs); } - return null; + return []; } - public function getPreservesSafety(): ?array + public function getPreservesSafety(): array { - return $this->options['preserves_safety']; + return $this->options['preserves_safety'] ?? []; } public function getPreEscape(): ?string diff --git a/data/web/inc/lib/vendor/twig/twig/src/TwigMacro.php b/data/web/inc/lib/vendor/twig/twig/src/TwigMacro.php new file mode 100644 index 000000000..c620fe82e --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/TwigMacro.php @@ -0,0 +1,201 @@ + + * + * @internal This class is an implementation detail of the per-template macro + * registry: compiled templates instantiate it, but it is not part of + * the public API, neither on 3.x nor in 4.0. + */ +final class TwigMacro +{ + /** + * @var array + */ + private array $argumentIndexes = []; + + /** + * @var array + */ + private array $requiredNames = []; + + /** + * Twig argument names that do not match their compiled closure parameter name + * (reserved names get prefixed by the compiler). Only needed by callLegacy()'s + * native binding; to be removed in 4.0 together with it. + * + * @var array + */ + private array $renamedArguments = []; + + private int $requiredCount = 0; + + /** + * @param \Closure(mixed...): (string|Markup) $body The compiled macro body, invoked with the bound arguments in definition order + * @param array $arguments The declared argument names mapped to whether they have a default value, in definition order + */ + public function __construct( + private string $name, + private \Closure $body, + private array $arguments = [], + private bool $variadic = false, + ) { + $i = 0; + foreach ($arguments as $argName => $hasDefault) { + $this->argumentIndexes[$argName] = $i; + if (\in_array($argName, TempNameExpression::RESERVED_NAMES, true)) { + $this->renamedArguments[$argName] = TempNameExpression::RESERVED_NAME_PREFIX.$argName; + } + if (!$hasDefault) { + $this->requiredCount = $i + 1; + $this->requiredNames[$argName] = true; + } + ++$i; + } + } + + /** + * Invokes the macro the way Twig 3.x always has (lenient argument handling), + * while reporting the cases that will become errors in Twig 4.0. + * + * In 4.0, this becomes the strict call() method: the scan below already detects + * every deprecated case, so triggerLegacyDeprecations() turns into upfront + * throws and the rest stays as is. + * + * @param array $arguments Positional arguments keyed by their integer position + * and named arguments keyed by their name + * @param Source $source The source of the template making the call, used to enrich errors + * @param int $lineno The line of the call in that template, used to enrich errors + */ + public function callLegacy(array $arguments, Source $source, int $lineno): string|Markup + { + // Spreading the arguments as-is is equivalent to resolving them: PHP binds + // them natively, maps the named ones onto their parameter, fills the + // defaults, and collects the extra ones into the variadic bucket. Only the + // cases where PHP would report its own error (with closure-centric wording) + // are detected upfront and turned into Twig errors. + if (array_is_list($arguments)) { + $count = \count($arguments); + if ($count < $this->requiredCount || (!$this->variadic && $count > \count($this->arguments))) { + $this->triggerLegacyDeprecations($arguments, $count, $source, $lineno); + } + + return ($this->body)(...$arguments); + } + + // Every declared argument is named: none can be missing, unknown, duplicated, or misordered. + if (\count($arguments) === \count($this->arguments) && !array_diff_key($arguments, $this->argumentIndexes)) { + return ($this->body)(...$this->renameArguments($arguments)); + } + + $positionalCount = 0; + $namedRequired = 0; + $sawNamed = false; + $misordered = false; + $duplicate = null; + $hasUnknownNamed = false; + foreach ($arguments as $key => $value) { + if (\is_int($key)) { + $misordered = $misordered || $sawNamed; + ++$positionalCount; + } else { + $sawNamed = true; + if (null === $i = $this->argumentIndexes[$key] ?? null) { + $hasUnknownNamed = true; + } else { + if (null === $duplicate && $i < $positionalCount) { + $duplicate = $key; + } + if (isset($this->requiredNames[$key])) { + ++$namedRequired; + } + } + } + } + + if ($misordered) { + throw new RuntimeError(\sprintf('Positional arguments cannot be used after named arguments for macro "%s".', $this->name), $lineno, $source); + } + if (null !== $duplicate) { + throw new RuntimeError(\sprintf('Argument "%s" is defined twice for macro "%s".', $duplicate, $this->name), $lineno, $source); + } + + // For a fully named call, the coverage of the required arguments is exact; a + // mixed call falls back to the precise (and slower) per-argument check. + $mayMissRequired = 0 === $positionalCount + ? $namedRequired < \count($this->requiredNames) + : $positionalCount < $this->requiredCount; + + if ($mayMissRequired || (!$this->variadic && ($hasUnknownNamed || $positionalCount > \count($this->arguments)))) { + $this->triggerLegacyDeprecations($arguments, $positionalCount, $source, $lineno); + } + + return ($this->body)(...$this->renameArguments($arguments)); + } + + /** + * @param array $arguments + * + * @return array + */ + private function renameArguments(array $arguments): array + { + foreach ($this->renamedArguments as $name => $parameterName) { + if (\array_key_exists($name, $arguments)) { + $arguments[$parameterName] = $arguments[$name]; + unset($arguments[$name]); + } + } + + return $arguments; + } + + /** + * To be removed in 4.0 (these deprecated cases become hard errors). + * + * @param array $arguments The full argument array; string keys are the named arguments + */ + private function triggerLegacyDeprecations(array $arguments, int $positionalCount, Source $source, int $lineno): void + { + if ($positionalCount < $this->requiredCount) { + foreach ($this->requiredNames as $argName => $required) { + if ($this->argumentIndexes[$argName] < $positionalCount || \array_key_exists($argName, $arguments)) { + continue; + } + + trigger_deprecation('twig/twig', '3.29', 'Not passing a value for the "%s" argument of macro "%s" is deprecated and the argument will be required in Twig 4.0; give it a default value in the macro definition or pass a value when calling it (in "%s" at line %d).', $argName, $this->name, $source->getName(), $lineno); + } + } + + if ($this->variadic) { + return; + } + + if ($positionalCount > \count($this->arguments)) { + trigger_deprecation('twig/twig', '3.29', 'Passing more arguments than the macro "%s" accepts is deprecated and will throw in Twig 4.0; declare a variadic argument ("...name") in the macro definition to accept extra arguments (in "%s" at line %d).', $this->name, $source->getName(), $lineno); + } + + foreach ($arguments as $name => $value) { + if (\is_string($name) && !isset($this->argumentIndexes[$name])) { + trigger_deprecation('twig/twig', '3.29', 'Passing the unknown named argument "%s" to the macro "%s" is deprecated and will throw in Twig 4.0; declare a variadic argument ("...name") in the macro definition to accept it (in "%s" at line %d).', $name, $this->name, $source->getName(), $lineno); + } + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php b/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php index 8811ca9c8..e3e1f82bd 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/CallableArgumentsExtractor.php @@ -40,22 +40,25 @@ final class CallableArgumentsExtractor public function extractArguments(Node $arguments): array { $extractedArguments = []; + $extractedArgumentNameMap = []; $named = false; foreach ($arguments as $name => $node) { if (!\is_int($name)) { $named = true; - $name = $this->normalizeName($name); } elseif ($named) { throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); } - $extractedArguments[$name] = $node; + $extractedArguments[$normalizedName = $this->normalizeName($name)] = $node; + $extractedArgumentNameMap[$normalizedName] = $name; } if (!$named && !$this->twigCallable->isVariadic()) { $min = $this->twigCallable->getMinimalNumberOfRequiredArguments(); if (\count($extractedArguments) < $this->rc->getReflector()->getNumberOfRequiredParameters() - $min) { - throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $this->rc->getReflector()->getParameters()[$min + \count($extractedArguments)]->getName(), $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + $argName = $this->toSnakeCase($this->rc->getReflector()->getParameters()[$min + \count($extractedArguments)]->getName()); + + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $argName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); } return $extractedArguments; @@ -71,37 +74,38 @@ final class CallableArgumentsExtractor [$callableParameters, $isPhpVariadic] = $this->getCallableParameters(); $arguments = []; - $names = []; + $callableParameterNames = []; $missingArguments = []; $optionalArguments = []; $pos = 0; foreach ($callableParameters as $callableParameter) { - $name = $this->normalizeName($callableParameter->name); + $callableParameterName = $callableParameter->name; if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { - if ('start' === $name) { - $name = 'low'; - } elseif ('end' === $name) { - $name = 'high'; + if ('start' === $callableParameterName) { + $callableParameterName = 'low'; + } elseif ('end' === $callableParameterName) { + $callableParameterName = 'high'; } } - $names[] = $name; + $callableParameterNames[] = $callableParameterName; + $normalizedCallableParameterName = $this->normalizeName($callableParameterName); - if (\array_key_exists($name, $extractedArguments)) { + if (\array_key_exists($normalizedCallableParameterName, $extractedArguments)) { if (\array_key_exists($pos, $extractedArguments)) { - throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); } if (\count($missingArguments)) { throw new SyntaxError(\sprintf( 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', - $name, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + $callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames)), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) ), $this->node->getTemplateLine(), $this->node->getSourceContext()); } $arguments = array_merge($arguments, $optionalArguments); - $arguments[] = $extractedArguments[$name]; - unset($extractedArguments[$name]); + $arguments[] = $extractedArguments[$normalizedCallableParameterName]; + unset($extractedArguments[$normalizedCallableParameterName]); $optionalArguments = []; } elseif (\array_key_exists($pos, $extractedArguments)) { $arguments = array_merge($arguments, $optionalArguments); @@ -116,9 +120,9 @@ final class CallableArgumentsExtractor break; } - $missingArguments[] = $name; + $missingArguments[] = $callableParameterName; } else { - throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); + throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $this->toSnakeCase($callableParameterName), $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext()); } } @@ -128,7 +132,13 @@ final class CallableArgumentsExtractor if (\is_int($key)) { $arbitraryArguments->addElement($value); } else { - $arbitraryArguments->addElement($value, new ConstantExpression($key, $this->node->getTemplateLine())); + $originalKey = $extractedArgumentNameMap[$key]; + if ($originalKey !== $this->toSnakeCase($originalKey)) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Using "snake_case" for variadic arguments is required for a smooth upgrade with Twig 4.0; rename "%s" to "%s" in "%s" at line %d.', $originalKey, $this->toSnakeCase($originalKey), $this->node->getSourceContext()->getName(), $this->node->getTemplateLine())); + } + $arbitraryArguments->addElement($value, new ConstantExpression($this->toSnakeCase($originalKey), $this->node->getTemplateLine())); + // I Twig 4.0, don't convert the key: + // $arbitraryArguments->addElement($value, new ConstantExpression($originalKey, $this->node->getTemplateLine())); } unset($extractedArguments[$key]); } @@ -151,7 +161,7 @@ final class CallableArgumentsExtractor throw new SyntaxError( \sprintf( 'Unknown argument%s "%s" for %s "%s(%s)".', - \count($extractedArguments) > 1 ? 's' : '', implode('", "', array_keys($extractedArguments)), $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', $names) + \count($extractedArguments) > 1 ? 's' : '', implode('", "', array_keys($extractedArguments)), $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames)) ), $unknownArgument ? $unknownArgument->getTemplateLine() : $this->node->getTemplateLine(), $unknownArgument ? $unknownArgument->getSourceContext() : $this->node->getSourceContext() @@ -163,27 +173,17 @@ final class CallableArgumentsExtractor private function normalizeName(string $name): string { - return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + return strtolower(str_replace('_', '', $name)); + } + + private function toSnakeCase(string $name): string + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z0-9])([A-Z])/'], '\1_\2', $name)); } private function getCallableParameters(): array { - $parameters = $this->rc->getReflector()->getParameters(); - if ($this->node->hasNode('node')) { - array_shift($parameters); - } - if ($this->twigCallable->needsCharset()) { - array_shift($parameters); - } - if ($this->twigCallable->needsEnvironment()) { - array_shift($parameters); - } - if ($this->twigCallable->needsContext()) { - array_shift($parameters); - } - foreach ($this->twigCallable->getArguments() as $argument) { - array_shift($parameters); - } + $parameters = $this->rc->getTwigParameters($this->node->hasNode('node')); $isPhpVariadic = false; if ($this->twigCallable->isVariadic()) { diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/CallableParameters.php b/data/web/inc/lib/vendor/twig/twig/src/Util/CallableParameters.php new file mode 100644 index 000000000..4321bb291 --- /dev/null +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/CallableParameters.php @@ -0,0 +1,161 @@ + + * + * @internal + */ +final class CallableParameters +{ + /** + * Returns the PHP parameters of a Filter/Function/Test call mapped to its + * template-level arguments. + * + * The parameters Twig injects automatically + * (`needs_charset/environment/context/is_sandboxed`) and the bound + * `arguments` are stripped. For a filter, the first returned parameter is + * the filter's input value. Returns null when reflection fails or the node + * is not a callable expression. + * + * @return list<\ReflectionParameter>|null + */ + public static function fromNode(Node $node, Environment $env): ?array + { + if (!$node instanceof FilterExpression && !$node instanceof FunctionExpression && !$node instanceof TestExpression) { + return null; + } + + $callable = self::resolveTwigCallable($node, $env); + if (null === $callable || null === $callable->getCallable()) { + return null; + } + + try { + return (new ReflectionCallable($callable))->getTwigParameters(); + } catch (\LogicException) { + return null; + } + } + + /** + * Returns true when a PHP parameter type proves the value reaching it + * cannot implicitly string-coerce (directly or by iterating it). + * + * Safe: `int`, `float`, `bool`, `null`, `false`, `true`, `void`, `never`, + * enums, and `final` class types that are neither `Stringable` nor + * `Traversable`. + * + * Unsafe: `string`, `array`, `iterable`, `mixed`, `object`, + * untyped, interfaces, non-final classes, and any type that is + * `Stringable` or `Traversable`. Interfaces and non-final classes are + * open: a subtype could add `Stringable`/`Traversable` and reach the host + * code, bypassing the `__toString` policy, so only a `final` class (enums + * included) is closed enough. + * + * @param \ReflectionClass|null $scope resolves the relative types `self`/`parent`/`static` (which + * older PHP versions report verbatim instead of the declaring class) + */ + public static function isStringCoercionSafe(?\ReflectionType $type, ?\ReflectionClass $scope = null): bool + { + if (null === $type) { + return false; + } + + // A union value is one of its members, so every member must be safe. + if ($type instanceof \ReflectionUnionType) { + foreach ($type->getTypes() as $t) { + if (!self::isStringCoercionSafe($t, $scope)) { + return false; + } + } + + return true; + } + + // An intersection value satisfies all its members at once. A safe + // member is necessarily a final class, which pins the concrete class, + // so the value is that non-coercible class whatever the other members. + if ($type instanceof \ReflectionIntersectionType) { + foreach ($type->getTypes() as $t) { + if (self::isStringCoercionSafe($t, $scope)) { + return true; + } + } + + return false; + } + + if (!$type instanceof \ReflectionNamedType) { + return false; + } + + $name = $type->getName(); + + if ($type->isBuiltin()) { + return match ($name) { + // `null` (e.g. as a union member) cannot have a __toString. + 'null', 'int', 'float', 'bool', 'true', 'false', 'void', 'never' => true, + default => false, // string, array, iterable, object, mixed, callable + }; + } + + // Resolve `self`/`parent`/`static` to the concrete class + // `static` is treated like `self` since a `final` class cannot be subclassed anyway + $class = match ($name) { + 'self', 'static' => $scope, + 'parent' => $scope ? ($scope->getParentClass() ?: null) : null, + default => class_exists($name, false) ? new \ReflectionClass($name) : null, + }; + + // Interfaces and non-final classes are open: a subtype could add + // Stringable/Traversable, so only a final non-coercible class is safe + if (null === $class) { + return false; + } + if (is_a($class->getName(), \Stringable::class, true) || is_a($class->getName(), \Traversable::class, true)) { + return false; + } + + return $class->isFinal(); + } + + private static function resolveTwigCallable(Node $node, Environment $env): ?TwigCallableInterface + { + if ($node->hasAttribute('twig_callable')) { + return $node->getAttribute('twig_callable'); + } + if (!$node->hasAttribute('name')) { + return null; + } + $name = $node->getAttribute('name'); + try { + return match (true) { + $node instanceof FilterExpression => $env->getFilter($name), + $node instanceof FunctionExpression => $env->getFunction($name), + $node instanceof TestExpression => $env->getTest($name), + }; + } catch (\Throwable) { + return null; + } + } +} diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php b/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php index 0ea26ed4b..64cbba5a3 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/DeprecationCollector.php @@ -54,7 +54,7 @@ final class DeprecationCollector public function collect(\Traversable $iterator): array { $deprecations = []; - set_error_handler(function ($type, $msg) use (&$deprecations) { + set_error_handler(static function ($type, $msg) use (&$deprecations) { if (\E_USER_DEPRECATED === $type) { $deprecations[] = $msg; } diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php b/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php index 16734d9df..9616e3b29 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/ReflectionCallable.php @@ -11,6 +11,7 @@ namespace Twig\Util; +use Twig\Node\Expression\CallExpression; use Twig\TwigCallableInterface; /** @@ -25,7 +26,7 @@ final class ReflectionCallable private $name; public function __construct( - TwigCallableInterface $twigCallable, + private TwigCallableInterface $twigCallable, ) { $callable = $twigCallable->getCallable(); if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { @@ -80,6 +81,44 @@ final class ReflectionCallable return $this->reflector; } + /** + * Returns the PHP parameters that map to the callable's template-level + * arguments. + * + * The parameters Twig injects automatically (the piped input value when + * $stripInput is true, then needs_charset/environment/context/is_sandboxed) + * and the bound arguments are stripped. + * + * @return list<\ReflectionParameter> + */ + public function getTwigParameters(bool $stripInput = false): array + { + $parameters = $this->reflector->getParameters(); + if ($stripInput) { + array_shift($parameters); + } + if ($this->twigCallable->needsCharset()) { + array_shift($parameters); + } + if ($this->twigCallable->needsEnvironment()) { + array_shift($parameters); + } + if ($this->twigCallable->needsContext()) { + array_shift($parameters); + } + if (CallExpression::needsIsSandboxed($this->twigCallable)) { + array_shift($parameters); + } + foreach ($this->twigCallable->getArguments() as $argument) { + array_shift($parameters); + } + + return array_values($parameters); + } + + /** + * @return callable + */ public function getCallable() { return $this->callable; diff --git a/data/web/inc/lib/vendor/twig/twig/src/Util/TemplateDirIterator.php b/data/web/inc/lib/vendor/twig/twig/src/Util/TemplateDirIterator.php index 3bef14bee..d739b285f 100644 --- a/data/web/inc/lib/vendor/twig/twig/src/Util/TemplateDirIterator.php +++ b/data/web/inc/lib/vendor/twig/twig/src/Util/TemplateDirIterator.php @@ -17,7 +17,7 @@ namespace Twig\Util; class TemplateDirIterator extends \IteratorIterator { /** - * @return mixed + * @return string */ #[\ReturnTypeWillChange] public function current() @@ -26,7 +26,7 @@ class TemplateDirIterator extends \IteratorIterator } /** - * @return mixed + * @return string */ #[\ReturnTypeWillChange] public function key()